Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 09:01
Оценка: 36 (2) :)
Есть вот такой метод:
void X::foo()
{
    void* p = this;
}


Компилятор студии (2005/2008) в дебаг режиме генерирует для него вот такой код:
    void* p = this;
  mov         eax,dword ptr [this] 
  sub         eax,4 
  mov         dword ptr [p],eax


Вопрос: откуда взялось смещение this на 4 при конвертации в void*?


1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Вопрос на засыпку
От: Кодт Россия  
Дата: 25.08.09 10:09
Оценка: 48 (2)
К>ЧЯДНТ?

Я туплю, вот что я делаю не так.

На стеке ещё валяется this от базы (сдвинутый), а Студия срезает угол: вместо того, чтобы сразу восстановить до текущего типа, она предпочитает восстанавливать в местах использования.

Окей, тогда вот такой код
struct X { virtual void* f()=0; };
struct Y { virtual void* f()=0; virtual void* g()=0; };

struct Z : X, Y
{
    virtual void* f() { return this; } // манёвры совершаются в функции-переходнике [thunk]:Z::f`adjustor{4}
    virtual void* g() { return this; } // совершаем манёвры прямо в теле функции
};

int main()
{
    Z z;
    Y* y = &z;
    return y->f() == y->g();
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[3]: Вопрос на засыпку
От: Аноним  
Дата: 25.08.09 09:35
Оценка: 42 (1) +1
R>Вопрос для тех, кто хочет проверить и посоревноваться в знании С++, и возможно узнать новую занятную деталь.

Может этот класс унаследован от нескольких, и эта функция второго базового, поэтому ей передается смещенный this, а при приведении к void* получаем указатель на "полный" объект.

А важно вообще, что это дебажный режим вижуалстудии? Может вижуалстудия что-то вспомогательное хранит, и непосредственно к с++ это отношения не имеет? Информации очень мало, и так угадывать — пальцем в небо.
Re: Вопрос на засыпку
От: Bell Россия  
Дата: 25.08.09 09:37
Оценка: 42 (1)
Здравствуйте, remark, Вы писали:

Дело в 4.10/2, а пример вот он:

struct Y1 
{
   virtual void foo() {}
};

struct X : virtual Y1
{
   void foo();
};

void X::foo()
{
   void* p = this;
}
Любите книгу — источник знаний (с) М.Горький
Re[2]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 10:26
Оценка: 12 (1)
Здравствуйте, remark, Вы писали:

R>>Вопрос: откуда взялось смещение this на 4 при конвертации в void*?


R>Занятный момент то, что статический тип 'this' есть X*. Хотя физически значение 'this' — указатель на другой объект Y. Соотв. компилятор при *любом* использовании 'this' "устраняет" это досадное недоразумение смещением значения на некоторую величину. Значение этой величины не обязательно sizeof(void*), а может быть любым.


Вообще изначально вопрос возник при разбирательстве, почему в следующем коде equal получается равным true. Это было бы логично, если бы статические типы this и other различались, ну там приведение между базовым и производным типами. А тут получается, что статические типы одинаковые, однако 2 физических не равных указателя при сравнении дают true.

void T::f()
{
  T* other = ...;
  // assume this = 0x1000, and other = 0x2000
  bool equal = (this == other);
  ...
}




1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: Вопрос на засыпку
От: Аноним  
Дата: 25.08.09 09:04
Оценка:
Здравствуйте, remark, Вы писали:

R>Есть вот такой метод:

R>
R>void X::foo()
R>{
R>    void* p = this;
R>}
R>


X — полиморф?
Re: Вопрос на засыпку
От: Аноним  
Дата: 25.08.09 09:06
Оценка:
А не указатель ли это на таблицу виртуальных функций? Попробуй это же для класса без вируальных функций.
Re[2]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 09:07
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, remark, Вы писали:


R>>Есть вот такой метод:

R>>
R>>void X::foo()
R>>{
R>>    void* p = this;
R>>}
R>>


А>X — полиморф?


Я думаю, что через 5 минут после публикации вопроса нецелесообразно давать какие-либо подсказки, т.к. это будет нечестно по отношению к тем, кто хочет выдать свой ответ без каких-либо подсказок.

1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 09:11
Оценка:
Здравствуйте, Аноним, Вы писали:

А>А не указатель ли это на таблицу виртуальных функций? Попробуй это же для класса без вируальных функций.


Поясню: я ответ знаю
Вопрос для тех, кто хочет проверить и посоревноваться в знании С++, и возможно узнать новую занятную деталь.

1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Вопрос на засыпку
От: R1K0 Россия  
Дата: 25.08.09 09:14
Оценка:
Честно скажу, что ответ не знаю.
Но размер 4 навевает мысли о теме адреса указателя...
Я хоть на правильном пути или как ?
Re[4]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 09:19
Оценка:
Здравствуйте, R1K0, Вы писали:

RK>Честно скажу, что ответ не знаю.

RK>Но размер 4 навевает мысли о теме адреса указателя...
RK>Я хоть на правильном пути или как ?

Я пока не буду давать никаких подсказок. Это будет не честно по отношению к тем, кто хочет решить без подсказок.
Если до завтра не будет ответа, то начну давать подсказки.

1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 09:44
Оценка:
Здравствуйте, Аноним, Вы писали:

R>>Вопрос для тех, кто хочет проверить и посоревноваться в знании С++, и возможно узнать новую занятную деталь.


А>Может этот класс унаследован от нескольких, и эта функция второго базового, поэтому ей передается смещенный this, а при приведении к void* получаем указатель на "полный" объект.


Засчитывается.
Вначале я хотел попросить пример кода. Но т.к. Bell уже привёл, смысла большого нет.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 09:46
Оценка:
Здравствуйте, Bell, Вы писали:

B>Здравствуйте, remark, Вы писали:


B>Дело в 4.10/2, а пример вот он:


Засчитывается.
Хотя я имел виду следующий код, суть одна и так же:

struct A
{
    virtual void foo() {}
};

struct B
{
    virtual void bar() {}
};

struct X : B, A
{
    void foo();
};

void X::foo()
{
    void* p = this;
}



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 09:51
Оценка:
Здравствуйте, remark, Вы писали:

R>Вопрос: откуда взялось смещение this на 4 при конвертации в void*?


Занятный момент то, что статический тип 'this' есть X*. Хотя физически значение 'this' — указатель на другой объект Y. Соотв. компилятор при *любом* использовании 'this' "устраняет" это досадное недоразумение смещением значения на некоторую величину. Значение этой величины не обязательно sizeof(void*), а может быть любым.
Соотв. это проявляется и в таком коде:
void X::foo()
{
   uintptr_t p = (uintptr_t)this;
   //mov         eax,dword ptr [this] 
   //sub         eax,4 
   //mov         dword ptr [p],eax 
}




1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Вопрос на засыпку
От: Кодт Россия  
Дата: 25.08.09 09:57
Оценка:
Здравствуйте, Bell, Вы писали:

B>Дело в 4.10/2, а пример вот он:


А можно пояснить? И кстати, натурные эксперименты с VC 2005 и gcc 3.4.4 не дали желанного сдвига.
ЧЯДНТ?
#include <iostream>
using namespace std;

struct X { virtual void foo() {} };
struct Z { int x; virtual ~Z() {} }; // пытался сдвинуть базу

struct Y : Z, X {
    void foo()
    {
        char* c = (char*)this; // на всякий случай, чтобы оббежать прямой кастинг Y* -> void*
        cout << this << " " << (void*)this << " " << (void*)c << endl;
        // 0x22cc50 0x22cc50 0x22cc50 -- адрес объекта Y целиком
    }
};

int main()
{
    Y y; y.foo();
    cout << &y << " " << &(X&)y << endl;
    // 0x22cc50 0x22cc58 -- адрес объекта Y и его базы X
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[3]: Вопрос на засыпку
От: Аноним  
Дата: 25.08.09 10:01
Оценка:
К>натурные эксперименты с VC 2005 и gcc 3.4.4 не дали желанного сдвига.

А если вместо c-style cast сделать dynamic_cast<void*>, что получится?

P.S. Попробовать самому сейчас нет возможности.
Re[2]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 10:02
Оценка:
Здравствуйте, Bell, Вы писали:

B>Здравствуйте, remark, Вы писали:


B>Дело в 4.10/2, а пример вот он:


Честно говоря, я немного затрудняюсь с ходу трактовать 4.10/2

4.10/2
An rvalue of type “pointer to cv T ,” where T is an effective object type, can be converted to an rvalue of
type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points
to the start of the storage location where the object of type T resides, as if the object is a most derived
object (1.8) of type T (that is, not a base class subobject). The null pointer value is converted to the null
pointer value of the destination type.


Разве указатель на объект как на MDT и как на подобъект другого объекта может различаться в принципе?

Я это смещение объяснил для себя несколько по-другому: как артефакт реализации виртуальных функций получаем, что в некоторой функции статический тип this есть T*, а реальное значение this получается указатель на какой-то другой объект Y*. Соотв. компилятору в реализации приходится это учитывать и смещать this при каждом использовании.
Следствие №1: это не регламентируется стандартом, т.к. реализация виртуальных функций не регламентируется.
Следствие №2: это проявляется не только при конвертации к void*, а при *любом* использовании. Например при конвертации к uintptr_t.
Следствие №3: вполне можно представить реализацию виртуальных функций, где статический и динамический типы this всегда будут совпадать, соотв. там не будет таких артефактов в принципе. Для этого, например, можно в таблице виртуальных функций вместе с каждым указателем хранить смещение от базового типа (где впервые объявлена виртуальная функция) до реального типа (в котором переопределена виртуальная функция). Тогда в месте вызова указатель будет соотв. смещаться. Хотя практически это наверное менее эффективно, чем делать это смещение уже внутри реальной функции.



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 10:05
Оценка:
Здравствуйте, Кодт, Вы писали:

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


B>>Дело в 4.10/2, а пример вот он:


К>А можно пояснить? И кстати, натурные эксперименты с VC 2005 и gcc 3.4.4 не дали желанного сдвига.

К>struct Y : Z, X {
К> void foo()
К> {
К> char* c = (char*)this; // на всякий случай, чтобы оббежать прямой кастинг Y* -> void*
К> cout << this << " " << (void*)this << " " << (void*)c << endl;
К> // 0x22cc50 0x22cc50 0x22cc50 -- адрес объекта Y целиком
К> }
К>};
К>[/ccode]


Всё правильно. Компилятор смещает this при *любом* использовании. Т.к. расхождение статического и динамического типа this — есть не более чем артефакт реализации виртуальных функций.
См мой комментарий ниже:
http://www.rsdn.ru/forum/cpp/3514848.1.aspx
Автор: remark
Дата: 25.08.09



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 10:24
Оценка:
Здравствуйте, Кодт, Вы писали:

К>На стеке ещё валяется this от базы (сдвинутый), а Студия срезает угол: вместо того, чтобы сразу восстановить до текущего типа, она предпочитает восстанавливать в местах использования.


Я думаю, что это связано исключительно с дебаг генерацией. В релиз логично смещать в начале функции. Хотя возможно по-месту всё равно будет эффективнее, если например мы имели mov eax, [ecx+20h], заменяем его просто на mov eax, [ecx+40h]. И никаких накладных расходов вообще.


К>Окей, тогда вот такой код


Занятный пример. Честно говоря, я не помню, что бы когда-либо сталкивался с adjustor служебной функцией.
Наверное adjustor всё равно эффективнее смещения this в месте вызова.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 10:29
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Окей, тогда вот такой код

К>
К>struct X { virtual void* f()=0; };
К>struct Y { virtual void* f()=0; virtual void* g()=0; };

К>struct Z : X, Y
К>{
К>    virtual void* f() { return this; } // манёвры совершаются в функции-переходнике [thunk]:Z::f`adjustor{4}
К>    virtual void* g() { return this; } // совершаем манёвры прямо в теле функции
К>};

К>int main()
К>{
К>    Z z;
К>    Y* y = &z;
К>    return y->f() == y->g();
К>}
К>



Как ты думаешь, это имеет какое-то отношение к 4.10/2:
http://www.rsdn.ru/forum/cpp/3514848.1.aspx
Автор: remark
Дата: 25.08.09

?


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: Вопрос на засыпку
От: Bell Россия  
Дата: 25.08.09 10:31
Оценка:
Здравствуйте, Кодт, Вы писали:

К>>ЧЯДНТ?


К>Я туплю, вот что я делаю не так.


Да уж, как говорится — не спешите выполнять приказание, возможно, что его еще отменят

В отладчике кстати видно, что значения указателей отличаются...
Любите книгу — источник знаний (с) М.Горький
Re[3]: Вопрос на засыпку
От: Bell Россия  
Дата: 25.08.09 10:49
Оценка:
Здравствуйте, remark, Вы писали:

R>Честно говоря, я немного затрудняюсь с ходу трактовать 4.10/2


Честно говоря, мне уже совсем не кажется, что дело в этом пункте.
Вернее даже так — дело совсем не в стандартном преобразовании к void*.
Любите книгу — источник знаний (с) М.Горький
Re[5]: Вопрос на засыпку
От: Кодт Россия  
Дата: 25.08.09 10:49
Оценка:
Здравствуйте, remark, Вы писали:

К>>Окей, тогда вот такой код


R>Занятный пример. Честно говоря, я не помню, что бы когда-либо сталкивался с adjustor служебной функцией.


Наверное, .asm-файлы редко смотрел и vftable не потрошил

R>Наверное adjustor всё равно эффективнее смещения this в месте вызова.


Для множественного перекрытия — это единственный выход (не считая извращений).
Поскольку Z::f перекрывает и X::f, и Y::f — она должна присутствовать в обеих таблицах.
Очевидно, что одно из этих присутствий опосредуется переходником, который корректно даункастит.

Вот если бы мы сделали
struct W { virtual void* e()=0; };
struct X { virtual void* f()=0; };
struct Y { virtual void* f()=0; };
struct Z : W, X, Y
{
    void* e() { return this; }
    void* f() { return this; }
    void* g() { return this; }
};

То вместо двух переходников (Z::X::f и Z::Y::f), кастящих к Z*, можно обойтись одним.

И действительно, Студия так и делает.
Z z;

z.e(); // кастинг Z->W тривиальный
z.f(); // кастинг Z->X (+4), в теле кастинг X->Z по месту (-4)
z.g(); // кастинг Z->Y (+8), в теле кастинг Y->Z по месту (-8)

Y& y = z; // Z->Y (+8)
y.f(); // в точке вызова кастинга нет, в переходнике кастинг Y->Z->X (-8+4 = -4), в теле кастинг X->Z по месту (-4)
y.g(); // в точке вызова кастинга нет, в теле кастинг Y->Z по месту (-8)
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[4]: Вопрос на засыпку
От: Кодт Россия  
Дата: 25.08.09 11:05
Оценка:
Здравствуйте, Bell, Вы писали:

R>>Честно говоря, я немного затрудняюсь с ходу трактовать 4.10/2


B>Честно говоря, мне уже совсем не кажется, что дело в этом пункте.

B>Вернее даже так — дело совсем не в стандартном преобразовании к void*.

В стандарте же не говорится, в каком виде this передавать. Конвенция thiscall — личное дело компилятора.
Вот захотел передавать на стеке и со смещением — ради бога.
Поскольку this — это значение, нигде официально не размещённое, то проверить его устройство мы не можем.

— Почему у твоей таксы такие короткие лапы?
— До земли достают, ну и достаточно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[3]: Вопрос на засыпку
От: Кодт Россия  
Дата: 25.08.09 11:05
Оценка:
Здравствуйте, remark, Вы писали:

R>Вообще изначально вопрос возник при разбирательстве, почему в следующем коде equal получается равным true. Это было бы логично, если бы статические типы this и other различались, ну там приведение между базовым и производным типами. А тут получается, что статические типы одинаковые, однако 2 физических не равных указателя при сравнении дают true.

R>
R>void T::f()
R>{
R>  T* other = ...;
R>  // assume this = 0x1000, and other = 0x2000
R>  bool equal = (this == other);
R>  ...
R>}
R>


Звучит как бред. Можешь привести рабочий пример, который валится на ассерте, хотя и не должен?
Или код и не должен валиться, но дебаггер при этом показывает ерунду?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[4]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 11:22
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Звучит как бред. Можешь привести рабочий пример, который валится на ассерте, хотя и не должен?

К>Или код и не должен валиться, но дебаггер при этом показывает ерунду?

Да, никакой код не валится. В итоге всё работает корректно. Я даже попытался дополнительно поломать код с помощью каких-то преобразований к void* и uintptr_t, но ничего не получилось. Пробовал делать типа такого:
uintptr_t p = (uintptr_t)this;
T* x = (T*)p;
assert(this == x);

Это работает как и ожидается, т.е. компилятор делает смещение при конвертации this к uintptr_t.

Однако дебаггер показывает различные адреса объектов, и в ассемблере они различны. Это и вызвано недоумение.
Кстати дебаггер корректно показывает содержимое объекта. Т.е. внутри X::foo() мы имеем this указывающий на другой объект Y, но дебаггер корректно показывает члены объект Х по такому указателю, т.е. он тоже неявно делает это смещение.



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Вопрос на засыпку
От: Аноним  
Дата: 25.08.09 11:45
Оценка:
Здравствуйте, remark, Вы писали:

R>Я думаю, что через 5 минут после публикации вопроса нецелесообразно давать какие-либо подсказки, т.к. это будет нечестно по отношению к тем, кто хочет выдать свой ответ без каких-либо подсказок.


Миль пардоньте, не догнал, что это cup of coffee task.
Но вы сами виноваты, могли бы с множественным и виртуальным наследованием поиграться, да еще и не на ia32 платформе, чтобы уши указателя не торчали так явно
Re[4]: Вопрос на засыпку
От: remark Россия http://www.1024cores.net/
Дата: 25.08.09 11:53
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, remark, Вы писали:


R>>Я думаю, что через 5 минут после публикации вопроса нецелесообразно давать какие-либо подсказки, т.к. это будет нечестно по отношению к тем, кто хочет выдать свой ответ без каких-либо подсказок.


А>Миль пардоньте, не догнал, что это cup of coffee task.

А>Но вы сами виноваты, могли бы с множественным и виртуальным наследованием поиграться, да еще и не на ia32 платформе, чтобы уши указателя не торчали так явно


Тут идёт смещение не на размер указателя, а на размер объекта. Т.е. добавив здесь
Автор: remark
Дата: 25.08.09
в один из объектов большой массив, можно получить смещение сколь угодно большое. Соотв. сделав смещение 4, я как раз стремился запутать ситуацию, типа смещение идёт на размер какого-то указателя. Если бы смещение было, например, 1234h, то было бы очевидно, что идёт смещение на размер объекта, а это уже даёт какую-то информацию. У меня же смещение идёт на какие-то абстрактные 4... Ну хотя, что кому будет сложнее наверное субъективно. Кого-то бы может наоборот 1234h ввело бы в ступор...



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.