Нелогичность static_cast
От: folk Россия  
Дата: 06.10.04 02:00
Оценка:
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?
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re: Нелогичность static_cast
От: lst Россия  
Дата: 06.10.04 04:09
Оценка:
Здравствуйте, 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*) ?

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]

Re[2]: Нелогичность static_cast
От: folk Россия  
Дата: 06.10.04 05:16
Оценка:
Здравствуйте, 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]

На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re: Нелогичность static_cast
От: Кодт Россия  
Дата: 06.10.04 10:09
Оценка: 1 (1)
Здравствуйте, 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 в адресном регистре, или за загрузку селектора сегмента, на который у программы нет прав?
Вот такая страшилка на ночь.
Перекуём баги на фичи!
Re[2]: Нелогичность static_cast
От: Antikrot  
Дата: 06.10.04 12:44
Оценка:
Здравствуйте, Кодт, Вы писали:

Сначала оговорюсь, что все нижесказанное верно, если вообще проц использует сегменты.

К>Но представим себе, что 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.
А вероятность такое схватить весьма небольшая... ведь объект-то на стеке, который растет от старших адресов к младшим.

К>Вот такая страшилка на ночь.
Re[3]: Нелогичность static_cast
От: Кодт Россия  
Дата: 06.10.04 13:07
Оценка: 10 (1)
Здравствуйте, Antikrot, Вы писали:

A>Сначала оговорюсь, что все нижесказанное верно, если вообще проц использует сегменты.


К>>Но представим себе, что b стоит на краю сегмента. Т.е. надо вычесть 1000 из адреса (dataseg,0). Что при этом получится? (dataseg,0xFFFFFE00)

A>Да.
K>> или переход на смежный сегмент (otherseg,0)?
A>Соответственно, нет.

К>>Даст ли процессор по пальцам за integer overflow в адресном регистре,

A>нет, не даст. потому что вычитание (с соответствующим overflow) будет проведено не в адресном регистре:
A>00411A10  mov         eax,dword ptr [pb] 
A>00411A13  sub         eax,100h 
A>00411A18  mov         dword ptr [ebp-0E8h],eax

Прекрасно, а кто мешает компилятору в дебаг-версии подклеить сюда же код проверки
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 ); // смотрим выше...
}
Перекуём баги на фичи!
Re[3]: Нелогичность static_cast
От: achp  
Дата: 06.10.04 16:33
Оценка:
Здравствуйте, Antikrot, Вы писали:

A>нет, не даст. потому что вычитание (с соответствующим overflow) будет проведено не в адресном регистре:


А откуда вы это знаете?

A>00411A10 mov eax,dword ptr [pb]

A>00411A13 sub eax,100h
A>00411A18 mov dword ptr [ebp-0E8h],eax

Отдаленно напоминает ассемблер, но на моей любимой платформе "Супер-пупер платформа" таких команд нет.
Я кончил, джентльмены, мне остается только поблагодарить вас за внимание.
Re[2]: Нелогичность static_cast
От: folk Россия  
Дата: 06.10.04 23:21
Оценка:
Здравствуйте, Кодт, Вы писали:

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; // неинициализированный указатель, как насчет загрузки селектора сегмента?
}


К>Вот такая страшилка на ночь.
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[4]: Нелогичность static_cast
От: folk Россия  
Дата: 06.10.04 23:31
Оценка:
Здравствуйте, Кодт, Вы писали:

К>>>Даст ли процессор по пальцам за integer overflow в адресном регистре,

A>>нет, не даст. потому что вычитание (с соответствующим overflow) будет проведено не в адресном регистре:
К>
A>>00411A10  mov         eax,dword ptr [pb] 
A>>00411A13  sub         eax,100h 
A>>00411A18  mov         dword ptr [ebp-0E8h],eax
К>

К>Прекрасно, а кто мешает компилятору в дебаг-версии подклеить сюда же код проверки
К>
К>jnc @okay
К>call overflow_in_address_arithmetics
К>


Да, никто не мешает.

[]

К>Ещё одна проблема — это возможность получить нулевой указатель.

К>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 представляются мне нелогичными.
А ведь сабж этого топика — нелогичность, буду стараться чтобы это показалось мне логичным
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[3]: Нелогичность static_cast
От: Павел Кузнецов  
Дата: 06.10.04 23:33
Оценка:
folk:

> Имхо, у такого указателя столько же "прав" на загрузку селектора сегмента, как и у этого:
> int main()
> {
>   SomeClass* p; // неинициализированный указатель, как насчет загрузки селектора сегмента?
> }
>


Не совсем. Значение данного указателя нигде не используется. Если начнет использоваться (например, копироваться) до инициализации — будут те же самые последствия по тем же самым причинам.
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[4]: Нелогичность static_cast
От: folk Россия  
Дата: 07.10.04 00:00
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>folk:


>> Имхо, у такого указателя столько же "прав" на загрузку селектора сегмента, как и у этого:
>> int main()
>> {
>>   SomeClass* p; // неинициализированный указатель, как насчет загрузки селектора сегмента?
>> }
>>


ПК>Не совсем. Значение данного указателя нигде не используется. Если начнет использоваться (например, копироваться) до инициализации — будут те же самые последствия по тем же самым причинам.


Мне совершенно ясно, что по стандарту этому указателю можно только присвоить значение, а результат всех остальных операций неопределен.
Я хотел показать, что все же бывают указатели, указывающие "куда попало" и само по себе их существование не приводит к загрузке селектора сегмента. И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[5]: Нелогичность static_cast
От: Павел Кузнецов  
Дата: 07.10.04 00:54
Оценка:
folk:

> Я хотел показать, что все же бывают указатели, указывающие "куда попало" и само по себе их существование не приводит к загрузке селектора сегмента.


Существование — нет. Использование его значения — имеет право. Скажем, static_cast таким использованием значения является.

> И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.


Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[2]: Нелогичность static_cast
От: folk Россия  
Дата: 07.10.04 07:30
Оценка: :)
Здравствуйте, Кодт, Вы писали:

К>Но представим себе, что b стоит на краю сегмента. Т.е. надо вычесть 1000 из адреса (dataseg,0). Что при этом получится? (dataseg,0xFFFFFE00) или переход на смежный сегмент (otherseg,0)?

К>Даст ли процессор по пальцам за integer overflow в адресном регистре, или за загрузку селектора сегмента, на который у программы нет прав?
К>Вот такая страшилка на ночь.

Я заглянул в C99, полагая что уж там-то можно указывать куда попало. Оказалось, в С приращение укзателя дальше чем на one past last element of array приводит к возможному переполнению и ub. Короче, я уверовал, что это не просто страшилки.
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[6]: Нелогичность static_cast
От: Кодт Россия  
Дата: 07.10.04 08:11
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

>> И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.


ПК>Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...


Тем более, что иногда компилятор использует адресную арифметику процессора в корыстных целях. Инструкция LEA, например.
Перекуём баги на фичи!
Re[3]: Нелогичность static_cast
От: Кодт Россия  
Дата: 07.10.04 08:15
Оценка:
Здравствуйте, folk, Вы писали:

F>Вобщем об этом и речь. Была бы удобной возможность откастить указатель в одном месте, хранить его в таком виде, и затем в другом месте корректно прикастить его обратно, с помощью того же static_cast или даже использованием указателя на член, который содержит корректное смещение this.


Какие проблемы?
Берёшь валидный объект, получаешь валидный указатель на него или на любой базовый подобъект и хранишь на здоровье. Затем выполняешь правильный даункаст — снова получаешь валидный указатель на исходный объект.

А всякие трюки с промежуточными невалидными значениями — нафига они нужны?
Перекуём баги на фичи!
Re[4]: Нелогичность static_cast
От: Antikrot  
Дата: 07.10.04 09:21
Оценка:
Здравствуйте, achp, Вы писали:

A>>нет, не даст. потому что вычитание (с соответствующим overflow) будет проведено не в адресном регистре:

A>А откуда вы это знаете?
Ну давайте тогда решим, какие регистры адресные...
типа ebp-esp? ну тогда подумай, зачем компилятору там чего-то считать? надо же будет сохранять-восстанавливать их предыдущие значения... не, конечно он может додуматься, что ничего восстанавливать не надо — не потребуется, но я таких не видел ни разу.

A>>00411A18 mov dword ptr [ebp-0E8h],eax

A>Отдаленно напоминает ассемблер, но на моей любимой платформе "Супер-пупер платформа" таких команд нет.
Предположим, на моей тоже . И предположим также, что для меня единственный адресный регистр — gp (global pointer) / Шутка /

Сорри за флейм...
Re[4]: Нелогичность static_cast
От: folk Россия  
Дата: 07.10.04 11:11
Оценка:
Здравствуйте, Кодт, Вы писали:

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 я здесь не вижу. Не то чтобы оно мне было нужно, просто стало немного обидно что так нельзя. Вот и все дела.
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[6]: Нелогичность static_cast
От: Шахтер Интернет  
Дата: 07.10.04 16:33
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>folk:


>> Я хотел показать, что все же бывают указатели, указывающие "куда попало" и само по себе их существование не приводит к загрузке селектора сегмента.


ПК>Существование — нет. Использование его значения — имеет право. Скажем, static_cast таким использованием значения является.


>> И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.


ПК>Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...


Вообще-то, оптимизатор должен оптимизировать код. Может вы подскажите, что это за чудо процессор такой, у которого загрузка сегментного регистра с проверкой селектора сегмента выполняется быстрее, чем простая загрузка регистра общего назначения? Очень хочу поглядеть на это чудо инженерной мысли.
... << RSDN@Home 1.1.0 stable >>
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re[7]: Нелогичность static_cast
От: Шахтер Интернет  
Дата: 07.10.04 16:33
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, Павел Кузнецов, Вы писали:


>>> И что само по себе приращение указателся до "невалидного" состояния без последующего разыменования также вряд ли приведет к загрузке. Имхо.


ПК>>Не обязательно разыменование. Можно и копирование... В присутствии оптимизации — вполне может. Всё зависит от сложности кода и логики оптимизатора...


К>Тем более, что иногда компилятор использует адресную арифметику процессора в корыстных целях. Инструкция LEA, например.


Не въехал, что значит "тем более".
... << RSDN@Home 1.1.0 stable >>
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re[3]: Нелогичность static_cast
От: Шахтер Интернет  
Дата: 07.10.04 16:33
Оценка: 10 (1)
Здравствуйте, folk, Вы писали:

F>Здравствуйте, Кодт, Вы писали:


К>>Но представим себе, что b стоит на краю сегмента. Т.е. надо вычесть 1000 из адреса (dataseg,0). Что при этом получится? (dataseg,0xFFFFFE00) или переход на смежный сегмент (otherseg,0)?

К>>Даст ли процессор по пальцам за integer overflow в адресном регистре, или за загрузку селектора сегмента, на который у программы нет прав?
К>>Вот такая страшилка на ночь.

F>Я заглянул в C99, полагая что уж там-то можно указывать куда попало. Оказалось, в С приращение укзателя дальше чем на one past last element of array приводит к возможному переполнению и ub. Короче, я уверовал, что это не просто страшилки.


Есть одна реальная возможность -- если в проце реализована арифметика с насыщением, к примеру, или взведен флаг, по которому генерируется исключение при переполнении в арифметических операциях.
Темне менее, на наиболее распространённых типах процессоров этой проблемы на самом деле нет.
... << RSDN@Home 1.1.0 stable >>
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.