struct B {};
struct D : B {};
int main()
{
B b;
B* pb1 = reinterpret_cast<B*>( reinterpret_cast<D*>( &b ) ); // pb1 == &b
B* pb2 = static_cast <B*>( static_cast <D*>( &b ) ); // значение pb2 неопределено согласно 5.2.9/1 :crash:
}
1. Почему так жестоко? Неужто нельзя было просто ограничить операции, допустимые над результатом static_cast<D*>(B*) ?
2. Может я все-таки ошибаюсь и там нет ub?
F>struct B {};
F>struct D : B {};
F>int main()
F>{
F> B b;
F> B* pb1 = reinterpret_cast<B*>( reinterpret_cast<D*>( &b ) ); // pb1 == &b
F> B* pb2 = static_cast <B*>( static_cast <D*>( &b ) ); // значение pb2 неопределено согласно 5.2.9/1 :crash:
F>}
F>
F>1. Почему так жестоко? Неужто нельзя было просто ограничить операции, допустимые над результатом static_cast<D*>(B*) ?
F>2. Может я все-таки ошибаюсь и там нет ub?
Есть согласно 5.2.9/8, т.к. в нашем случае не удовлетворяется условие:
[skipped]
If the rvalue of type "pointer to cv1 B" points to the B that is actually a sub-object of an object of type D, the resulting pointer points to the enclosing object of type D.
[skipped]
Здравствуйте, lst, Вы писали:
lst>Здравствуйте, folk, Вы писали:
F>>
F>>struct B {};
F>>struct D : B {};
F>>int main()
F>>{
F>> B b;
F>> B* pb1 = reinterpret_cast<B*>( reinterpret_cast<D*>( &b ) ); // pb1 == &b
F>> B* pb2 = static_cast <B*>( static_cast <D*>( &b ) ); // значение pb2 неопределено согласно 5.2.9/1 :crash:
F>>}
F>>
F>>1. Почему так жестоко? Неужто нельзя было просто ограничить операции, допустимые над результатом static_cast<D*>(B*) ? lst> F>>2. Может я все-таки ошибаюсь и там нет ub? lst>Есть согласно 5.2.9/8, т.к. в нашем случае не удовлетворяется условие:
Да, 5.2.9/1 было опиской, конечно же 5.2.9/8
lst>
lst>[skipped]
lst>If the rvalue of type "pointer to cv1 B" points to the B that is actually a sub-object of an object of type D, the resulting pointer points to the enclosing object of type D.
lst>[skipped]
Здравствуйте, folk, Вы писали:
F>1. Почему так жестоко? Неужто нельзя было просто ограничить операции, допустимые над результатом static_cast<D*>(B*) ? F>2. Может я все-таки ошибаюсь и там нет ub?
Там UB, но "мягкое".
Работа static_cast сводится к смещению (при множественном наследовании, например).
В большинстве случаев это "не страшно", если не пытаться разыменовывать полученные заведомо инвалидные указатели.
Однако, получение инвалидного указателя — само по себе UB.
#pragma pack(1)
struct A { char x[0x100]; };
struct B { int y; };
struct C : A, B {};
B b;
main()
{
B* pb = &b;
C* pc = static_cast<C*>(pb);
char* ps = reinterpret_cast<char*>(pb) - 0x100;
// значения pc и ps совпадают
};
Но представим себе, что b стоит на краю сегмента. Т.е. надо вычесть 1000 из адреса (dataseg,0). Что при этом получится? (dataseg,0xFFFFFE00) или переход на смежный сегмент (otherseg,0)?
Даст ли процессор по пальцам за integer overflow в адресном регистре, или за загрузку селектора сегмента, на который у программы нет прав?
Вот такая страшилка на ночь.
Сначала оговорюсь, что все нижесказанное верно, если вообще проц использует сегменты.
К>Но представим себе, что b стоит на краю сегмента. Т.е. надо вычесть 1000 из адреса (dataseg,0). Что при этом получится? (dataseg,0xFFFFFE00)
Да. K> или переход на смежный сегмент (otherseg,0)?
Соответственно, нет.
К>Даст ли процессор по пальцам за integer overflow в адресном регистре,
нет, не даст. потому что вычитание (с соответствующим overflow) будет проведено не в адресном регистре:
00411A10 mov eax,dword ptr [pb]
00411A13 sub eax,100h
00411A18 mov dword ptr [ebp-0E8h],eax
K>или за загрузку селектора сегмента, на который у программы нет прав?
Вот что-то типа того. Короче, access violation.
А вероятность такое схватить весьма небольшая... ведь объект-то на стеке, который растет от старших адресов к младшим.
К>Вот такая страшилка на ночь.
Здравствуйте, Antikrot, Вы писали:
A>Сначала оговорюсь, что все нижесказанное верно, если вообще проц использует сегменты.
К>>Но представим себе, что b стоит на краю сегмента. Т.е. надо вычесть 1000 из адреса (dataseg,0). Что при этом получится? (dataseg,0xFFFFFE00) A>Да. K>> или переход на смежный сегмент (otherseg,0)? A>Соответственно, нет.
К>>Даст ли процессор по пальцам за integer overflow в адресном регистре, A>нет, не даст. потому что вычитание (с соответствующим overflow) будет проведено не в адресном регистре:
Прекрасно, а кто мешает компилятору в дебаг-версии подклеить сюда же код проверки
jnc @okay
call overflow_in_address_arithmetics
K>>или за загрузку селектора сегмента, на который у программы нет прав? A>Вот что-то типа того. Короче, access violation. A>А вероятность такое схватить весьма небольшая... ведь объект-то на стеке, который растет от старших адресов к младшим.
Это допущение. Как устроен стек на конкретной платформе — большой-пребольшой вопрос.
Ещё одна проблема — это возможность получить нулевой указатель.
static_cast правильно приводит нуль-указатели туда-сюда
struct A {...};
struct B {...};
struct C : A, B {...};
int main()
{
C c;
C* pc1 = &c;
B* pb1 = pc1; // неявный static_cast
C* pd1 = static_cast<C*>(pb1); // d значит downcast
assert( pd1 == pc1 );
C* pc0 = NULL;
B* pb0 = pc0;
assert( pb0 == NULL );
C* pd0 = static_cast<C*>(pb0);
assert( pd0 == NULL );
static B b;
B* pb2 = &b;
C* pd2 = static_cast<B*>(pb2);
assert( pd2 != NULL ); // увы, таких гарантий нет.
B* pu2 = pd2; // если pd2 == 0, то и pu2 == 0
assert( pb2 == pu2 ); // смотрим выше...
}
Здравствуйте, Кодт, Вы писали:
F>>1. Почему так жестоко? Неужто нельзя было просто ограничить операции, допустимые над результатом static_cast<D*>(B*) ? F>>2. Может я все-таки ошибаюсь и там нет ub?
К>Там UB, но "мягкое". К>Работа static_cast сводится к смещению (при множественном наследовании, например).
Вобщем об этом и речь. Была бы удобной возможность откастить указатель в одном месте, хранить его в таком виде, и затем в другом месте корректно прикастить его обратно, с помощью того же static_cast или даже использованием указателя на член, который содержит корректное смещение this.
К>В большинстве случаев это "не страшно", если не пытаться разыменовывать полученные заведомо инвалидные указатели.
Жаль нет гарантий.
К>Однако, получение инвалидного указателя — само по себе UB. К>
К>#pragma pack(1)
К>struct A { char x[0x100]; };
К>struct B { int y; };
К>struct C : A, B {};
К>B b;
К>main()
К>{
К> B* pb = &b;
К> C* pc = static_cast<C*>(pb);
К> char* ps = reinterpret_cast<char*>(pb) - 0x100;
К> // значения pc и ps совпадают
К>};
К>
К>Но представим себе, что b стоит на краю сегмента. Т.е. надо вычесть 1000 из адреса (dataseg,0). Что при этом получится? (dataseg,0xFFFFFE00) или переход на смежный сегмент (otherseg,0)? К>Даст ли процессор по пальцам за integer overflow в адресном регистре, или за загрузку селектора сегмента, на который у программы нет прав?
Согласен с Antikrot, что нашему указателю нечего делать в адресном регистре, до тех пор пока над ним не производятся операции, связанные с его разыменованием.
Имхо, у такого указателя столько же "прав" на загрузку селектора сегмента, как и у этого:
int main()
{
SomeClass* p; // неинициализированный указатель, как насчет загрузки селектора сегмента?
}
Здравствуйте, Кодт, Вы писали:
К>>>Даст ли процессор по пальцам за integer overflow в адресном регистре, A>>нет, не даст. потому что вычитание (с соответствующим overflow) будет проведено не в адресном регистре: К>
[]
К>Ещё одна проблема — это возможность получить нулевой указатель. К>static_cast правильно приводит нуль-указатели туда-сюда К>
К>struct A {...};
К>struct B {...};
К>struct C : A, B {...};
К>int main()
К>{
К> C c;
К> C* pc1 = &c;
К> B* pb1 = pc1; // неявный static_cast
К> C* pd1 = static_cast<C*>(pb1); // d значит downcast
К> assert( pd1 == pc1 );
К> C* pc0 = NULL;
К> B* pb0 = pc0;
К> assert( pb0 == NULL );
К> C* pd0 = static_cast<C*>(pb0);
К> assert( pd0 == NULL );
К> static B b;
К> B* pb2 = &b;
К> C* pd2 = static_cast<B*>(pb2);
К> assert( pd2 != NULL ); // увы, таких гарантий нет.
К> B* pu2 = pd2; // если pd2 == 0, то и pu2 == 0
К> assert( pb2 == pu2 ); // смотрим выше...
К>}
К>
Упс, об этом я не подумал. Действительно, VC при static_cast проверяет указатель на ноль, и либо смещает его, либо оставляет нулем.
Видимо я "по старинке" провожу аналогию между операциями над значениями указателей и беззнаковой арифметикой. И поэтому специальные проверки на переполнение или сравнение указателя с нулем при static_cast представляются мне нелогичными.
А ведь сабж этого топика — нелогичность, буду стараться чтобы это показалось мне логичным
folk:
> Имхо, у такого указателя столько же "прав" на загрузку селектора сегмента, как и у этого:
> int main()
> {
> SomeClass* p; // неинициализированный указатель, как насчет загрузки селектора сегмента?
> }
>
Не совсем. Значение данного указателя нигде не используется. Если начнет использоваться (например, копироваться) до инициализации — будут те же самые последствия по тем же самым причинам.
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>folk:
>> Имхо, у такого указателя столько же "прав" на загрузку селектора сегмента, как и у этого:
>> int main()
>> {
>> SomeClass* p; // неинициализированный указатель, как насчет загрузки селектора сегмента?
>> }
>>
ПК>Не совсем. Значение данного указателя нигде не используется. Если начнет использоваться (например, копироваться) до инициализации — будут те же самые последствия по тем же самым причинам.
Мне совершенно ясно, что по стандарту этому указателю можно только присвоить значение, а результат всех остальных операций неопределен.
Я хотел показать, что все же бывают указатели, указывающие "куда попало" и само по себе их существование не приводит к загрузке селектора сегмента. И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.
folk:
> Я хотел показать, что все же бывают указатели, указывающие "куда попало" и само по себе их существование не приводит к загрузке селектора сегмента.
Существование — нет. Использование его значения — имеет право. Скажем, static_cast таким использованием значения является.
> И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.
Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Кодт, Вы писали:
К>Но представим себе, что b стоит на краю сегмента. Т.е. надо вычесть 1000 из адреса (dataseg,0). Что при этом получится? (dataseg,0xFFFFFE00) или переход на смежный сегмент (otherseg,0)? К>Даст ли процессор по пальцам за integer overflow в адресном регистре, или за загрузку селектора сегмента, на который у программы нет прав? К>Вот такая страшилка на ночь.
Я заглянул в C99, полагая что уж там-то можно указывать куда попало. Оказалось, в С приращение укзателя дальше чем на one past last element of array приводит к возможному переполнению и ub. Короче, я уверовал, что это не просто страшилки.
Здравствуйте, Павел Кузнецов, Вы писали:
>> И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.
ПК>Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...
Тем более, что иногда компилятор использует адресную арифметику процессора в корыстных целях. Инструкция LEA, например.
Здравствуйте, folk, Вы писали:
F>Вобщем об этом и речь. Была бы удобной возможность откастить указатель в одном месте, хранить его в таком виде, и затем в другом месте корректно прикастить его обратно, с помощью того же static_cast или даже использованием указателя на член, который содержит корректное смещение this.
Какие проблемы?
Берёшь валидный объект, получаешь валидный указатель на него или на любой базовый подобъект и хранишь на здоровье. Затем выполняешь правильный даункаст — снова получаешь валидный указатель на исходный объект.
А всякие трюки с промежуточными невалидными значениями — нафига они нужны?
Здравствуйте, achp, Вы писали:
A>>нет, не даст. потому что вычитание (с соответствующим overflow) будет проведено не в адресном регистре: A>А откуда вы это знаете?
Ну давайте тогда решим, какие регистры адресные...
типа ebp-esp? ну тогда подумай, зачем компилятору там чего-то считать? надо же будет сохранять-восстанавливать их предыдущие значения... не, конечно он может додуматься, что ничего восстанавливать не надо — не потребуется, но я таких не видел ни разу.
A>>00411A18 mov dword ptr [ebp-0E8h],eax A>Отдаленно напоминает ассемблер, но на моей любимой платформе "Супер-пупер платформа" таких команд нет.
Предположим, на моей тоже . И предположим также, что для меня единственный адресный регистр — gp (global pointer) / Шутка /
Здравствуйте, Кодт, Вы писали:
F>>Вобщем об этом и речь. Была бы удобной возможность откастить указатель в одном месте, хранить его в таком виде, и затем в другом месте корректно прикастить его обратно, с помощью того же static_cast или даже использованием указателя на член, который содержит корректное смещение this.
К>Какие проблемы? К>Берёшь валидный объект, получаешь валидный указатель на него или на любой базовый подобъект и хранишь на здоровье. Затем выполняешь правильный даункаст — снова получаешь валидный указатель на исходный объект.
Ну разумеется ты прав.
К>А всякие трюки с промежуточными невалидными значениями — нафига они нужны?
Ок, попытаюсь объяснить откуда ересь.
На днях здесь вспыла вечноживая тема "как сделать closure". Подумался очередной вариант — с помощью извращенного кросс-каста — приводить указатель на любой класс к посторонней "общей" базе и соответствующим образом подстраивать указатель на функцию-член. Типа этого:
class Closure
{
public:
typedef int Func(int);
template<class T> Closure(T*, Func T::*);
int operator()(int i) const { return ( object_->*method_ )( i ); }
private:
// Наследование от A и B нужно, чтобы VC использовал для CommonBase
// multiple inheritance member pointer representation без всяких прагм.
// Для других компиляторов этого может не понадобиться, но хуже от этого не будет.class A {};
class B {};
class CommonBase : A, B {};
CommonBase* object_;
Func CommonBase::* method_;
};
template<class T>
Closure::Closure(T* object, Func T::* method)
{
class CommonDeriver : public T, public CommonBase {};
Func CommonDeriver::* pmf = method;
this->method_ = static_cast<Func CommonBase::*>( pmf );
CommonDeriver* po = static_cast<CommonDeriver*>( object ); // ub, обидно однакоthis->object_ = po;
}
используем:
#include<iostream>
struct Test
{
int i;
int add(int j) { return i + j; }
};
int main()
{
Test t = { 5 };
Closure c( &t, &Test::add );
std::cout << c( 2 ) << '\n';
return 0;
}
Где ub понятно, других ub я здесь не вижу. Не то чтобы оно мне было нужно, просто стало немного обидно что так нельзя. Вот и все дела.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>folk:
>> Я хотел показать, что все же бывают указатели, указывающие "куда попало" и само по себе их существование не приводит к загрузке селектора сегмента.
ПК>Существование — нет. Использование его значения — имеет право. Скажем, static_cast таким использованием значения является.
>> И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.
ПК>Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...
Вообще-то, оптимизатор должен оптимизировать код. Может вы подскажите, что это за чудо процессор такой, у которого загрузка сегментного регистра с проверкой селектора сегмента выполняется быстрее, чем простая загрузка регистра общего назначения? Очень хочу поглядеть на это чудо инженерной мысли.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Павел Кузнецов, Вы писали:
>>> И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.
ПК>>Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...
К>Тем более, что иногда компилятор использует адресную арифметику процессора в корыстных целях. Инструкция LEA, например.
Здравствуйте, folk, Вы писали:
F>Здравствуйте, Кодт, Вы писали:
К>>Но представим себе, что b стоит на краю сегмента. Т.е. надо вычесть 1000 из адреса (dataseg,0). Что при этом получится? (dataseg,0xFFFFFE00) или переход на смежный сегмент (otherseg,0)? К>>Даст ли процессор по пальцам за integer overflow в адресном регистре, или за загрузку селектора сегмента, на который у программы нет прав? К>>Вот такая страшилка на ночь.
F>Я заглянул в C99, полагая что уж там-то можно указывать куда попало. Оказалось, в С приращение укзателя дальше чем на one past last element of array приводит к возможному переполнению и ub. Короче, я уверовал, что это не просто страшилки.
Есть одна реальная возможность -- если в проце реализована арифметика с насыщением, к примеру, или взведен флаг, по которому генерируется исключение при переполнении в арифметических операциях.
Темне менее, на наиболее распространённых типах процессоров этой проблемы на самом деле нет.
Шахтер:
> ПК>Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...
> Вообще-то, оптимизатор должен оптимизировать код.
При этом оптимизация может быть как по размеру, так и по скорости. И для обоих случаев можно вполне представить примеры, когда ранняя загрузка сегментного регистра может быть выгоднее, чем поздняя.
> Может вы подскажите, что это за чудо процессор такой, у которого загрузка сегментного регистра с проверкой селектора сегмента выполняется быстрее, чем простая загрузка регистра общего назначения?
Давай представим, что загрузка сегментного регистра в 10000 раз медленее, чем загрузка регистра общего назначения — так даже нагляднее получится. И представим такой гипотетический пример:
int f(A* a, B* b, int size)
{
int result = 0;
for (int i = 0; i < size; ++i)
{
if (a->f(i))
result += a->g(i, b);
else
result += b->h(i);
}
}
Можно легко заметить, что непосредственно в данной функции b разыменовывается только в одной из веток. Но если в результате какой-нибудь profile-guided оптимизации выяснится, что вынесение загрузки b в сегментный регистр за пределы цикла уменьшает среднее время исполнения этой функции в 10 раз, то я вполне пойму логику оптимизатора, загружающего сегментный регистр заранее, даже если иногда при вызове этой функции ветка с разыменованием b вообще не будет выполняться.
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Шахтер:
>> ПК>Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...
>> Вообще-то, оптимизатор должен оптимизировать код.
ПК>При этом оптимизация может быть как по размеру, так и по скорости. И для обоих случаев можно вполне представить примеры, когда ранняя загрузка сегментного регистра может быть выгоднее, чем поздняя.
>> Может вы подскажите, что это за чудо процессор такой, у которого загрузка сегментного регистра с проверкой селектора сегмента выполняется быстрее, чем простая загрузка регистра общего назначения?
ПК>Давай представим, что загрузка сегментного регистра в 10000 раз медленее, чем загрузка регистра общего назначения — так даже нагляднее получится. И представим такой гипотетический пример: ПК>
ПК>int f(A* a, B* b, int size)
ПК>{
ПК> int result = 0;
ПК> for (int i = 0; i < size; ++i)
ПК> {
ПК> if (a->f(i))
ПК> result += a->g(i, b);
ПК> else
ПК> result += b->h(i);
ПК> }
ПК>}
ПК>
ПК>Можно легко заметить, что непосредственно в данной функции b разыменовывается только в одной из веток. Но если в результате какой-нибудь profile-guided оптимизации выяснится, что вынесение загрузки b в сегментный регистр за пределы цикла уменьшает среднее время исполнения этой функции в 10 раз, то я вполне пойму логику оптимизатора, загружающего сегментный регистр заранее, даже если иногда при вызове этой функции ветка с разыменованием b вообще не будет выполняться.
Боюсь, что пример не корректен. Если я передам в b нулевой укащатель(а это вполне законно), то при ранней загрузке сегментного регистра получу исключение. Так что данная оптимизация некорректна.
А вообще, с тяжелыми сегментными регистрами так не работают. Их обычно загружают вручную и не меняют без нужды.
Вообщем, я пока не вижу большой пользы от запрета копирования неинициализированных фундаментальных типов, включая указатели. Тем более, что наиболее распространённые платформы это допускают.
Шахтер:
> Боюсь, что пример не корректен. Если я передам в b нулевой укащатель(а это вполне законно), то при ранней загрузке сегментного регистра получу исключение. Так что данная оптимизация некорректна.
Оптимизатор вполне может включить проверку на 0
> Вообщем, я пока не вижу большой пользы от запрета копирования неинициализированных фундаментальных типов, включая указатели. Тем более, что наиболее распространённые платформы это допускают.
Для платформ, на которых это допустимо, неопределенное поведение всегда можно определить. А вот что делать с теми платформами, у которых подобные операции недопустимы?
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Шахтер:
>> Боюсь, что пример не корректен. Если я передам в b нулевой укащатель(а это вполне законно), то при ранней загрузке сегментного регистра получу исключение. Так что данная оптимизация некорректна.
ПК>Оптимизатор вполне может включить проверку на 0
Т.е. фактически, данный вид оптимизации будет базироваться на запрете копирования неинициализированных указателей. А теперь вынесем этот код из функции в блок
{
...
{
A* a=что-то, B* b, int size=что-то;
int result = 0;
for (int i = 0; i < size; ++i)
{
if (a->f(i))
result += a->g(i, b);
else
result += b->h(i);
}
}
...
}
Этот фрагмент не содержит UB, если else не выполняется. Т.е. его оптимизировать таким способом уже нельзя. Хождение по краю какое-то.
>> Вообщем, я пока не вижу большой пользы от запрета копирования неинициализированных фундаментальных типов, включая указатели. Тем более, что наиболее распространённые платформы это допускают.
ПК>Для платформ, на которых это допустимо, неопределенное поведение всегда можно определить. А вот что делать с теми платформами, у которых подобные операции недопустимы?
А между прочим, есть выход. Уровни конформантности. Т.е. компилятор должен явно декларировать некоторые свойства платформы. Скажем, путем определения макросимволов. В тот же __cplusplus можно зафигачить набор флагов. И выбирать код, соответствующий платформе.
Шахтер:
> Т.е. фактически, данный вид оптимизации будет базироваться на запрете копирования неинициализированных указателей. А теперь вынесем этот код из функции в блок >
> {
> ...
> {
> A* a=что-то, B* b, int size=что-то;
>
> int result = 0;
> for (int i = 0; i < size; ++i)
> {
> if (a->f(i))
> result += a->g(i, b);
> else
> result += b->h(i);
> }
> }
> ...
> }
>
> Этот фрагмент не содержит UB, если else не выполняется
Почему не содержит? Копирование невалидного указателя все равно происходит...
> ПК>Для платформ, на которых это допустимо, неопределенное поведение всегда можно определить. А вот что делать с теми платформами, у которых подобные операции недопустимы?
> А между прочим, есть выход. Уровни конформантности. Т.е. компилятор должен явно декларировать некоторые свойства платформы. Скажем, путем определения макросимволов. В тот же __cplusplus можно зафигачить набор флагов. И выбирать код, соответствующий платформе.
На практике это приведет к провоцированию появления множества диалектов языка, что вряд ли можно назвать хорошим делом.
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Шахтер:
>> Т.е. фактически, данный вид оптимизации будет базироваться на запрете копирования неинициализированных указателей. А теперь вынесем этот код из функции в блок >>
>> {
>> ...
>> {
>> A* a=что-то, B* b, int size=что-то;
>>
>> int result = 0;
>> for (int i = 0; i < size; ++i)
>> {
>> if (a->f(i))
>> result += a->g(i, b);
>> else
>> result += b->h(i);
>> }
>> }
>> ...
>> }
>>
>> Этот фрагмент не содержит UB, если else не выполняется
ПК>Почему не содержит? Копирование невалидного указателя все равно происходит...
Вот так тогда.
{
...
{
A* a=что-то, B* b, int size=что-то;
int result = 0;
for (int i = 0; i < size; ++i)
{
if (a->f(i))
result += a->g(i);
else
result += b->h(i);
}
}
...
}
Кстати, я что-то протормозил с проверкой на нуль. Это сделать конечно можно, но придётся код цикла сдубрировать, поскольку он будет завязан на предположение о предзагрузке, а в случае нулевого указателя это предположение неверно.
>> ПК>Для платформ, на которых это допустимо, неопределенное поведение всегда можно определить. А вот что делать с теми платформами, у которых подобные операции недопустимы?
>> А между прочим, есть выход. Уровни конформантности. Т.е. компилятор должен явно декларировать некоторые свойства платформы. Скажем, путем определения макросимволов. В тот же __cplusplus можно зафигачить набор флагов. И выбирать код, соответствующий платформе.
ПК>На практике это приведет к провоцированию появления множества диалектов языка, что вряд ли можно назвать хорошим делом.
ЭЭЭ. Да эти диалекты уже существуют по факту. Поздно пить боржоми. Совсем поздно. Даже гемодиализ не спасет.
Как раз наоборот, было бы полезно этот дикий сейчас процесс ввести в нормальное русло -- т.е. оформить де юре, то что уже и так существует де факто. Это даст возможность упростить перенос кода между платформами, потому что специфические куски кода будут явно выделены.
Имо, в данном случае это экономия на спичках, от которой мало пользы. И вообще, когда несколько месяцев назад смотрел yasli::vector, сложилось впечатление, что ясландеры иногда уделяют слишком много внимания всякой мелочной фигне, вместо того чтобы потратить время на что-нибудь более полезное.
Например запомнилась специализация функции destroy для примитивных типов, которая ничего не делает. Вменяемый оптимизатор и сам сможет выкинуть цикл for( ; p != end; ++p ) p->~T(); если T имеет тривиальный деструктор. А значит нефиг усложнять код ненужными подробностями.
мы имеем одну операцию чтения из памяти и две записи.
В этом варианте
ebo_.beg_ = 0;
end_ = 0;
eos_ = 0;
три операции записи. Скорее всего, тоже на тоже и выйдет. А, кроме того, если посмотреть другие методы, например empty() или деструктор, то там теперь приходится делать две операции чтения из памяти вместо одной, т.е. идёт проигрышь. Суммарный эффект, скорее всего, будет отрицательный.
F>И вообще, когда несколько месяцев назад смотрел yasli::vector, сложилось впечатление, что ясландеры иногда уделяют слишком много внимания всякой мелочной фигне, вместо того чтобы потратить время на что-нибудь более полезное.
Да, есть такое ощущение. Похоже, у Александреску остроумия больше, чем здравого смысла.
F>Например запомнилась специализация функции destroy для примитивных типов, которая ничего не делает. Вменяемый оптимизатор и сам сможет выкинуть цикл for( ; p != end; ++p ) p->~T(); если T имеет тривиальный деструктор. А значит нефиг усложнять код ненужными подробностями.