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. Короче, я уверовал, что это не просто страшилки.
Есть одна реальная возможность -- если в проце реализована арифметика с насыщением, к примеру, или взведен флаг, по которому генерируется исключение при переполнении в арифметических операциях.
Темне менее, на наиболее распространённых типах процессоров этой проблемы на самом деле нет.