Здравствуйте Alex Fedotov, вы писали:
AF>Еще раз, медленно. Никто не говорит, что любой указатель на x86 64 бита. AF>Но указатель на функцию-член класса действительно 8 байт (собственно AF>указатель + смещение от this), что проверяется простым sizeof и от AF>#pragma pack это не зависит.
Мне кажется, нужно всё-таки внести окончательную ясность по данному вопросу. Иначе эта дискуссия с цитированием ассемблерных листингов собьёт с толку кого угодно. :)
В большинстве случаев указатель на невиртуальную функцию-член всё-таки занимает 4 байта — только адрес функции. Дополнительные 4 байта — смещение — возникают только при множественном наследовании. При обычном наследовании смещение не нужно, так оно всегда равнялось бы нулю, и мы просто без нужды тратили бы память.
Поясню, зачем вообще понадобилось это смещение. Дело в том, что переменные-члены классов, образующих цепочку наследования, располагаются в памяти последовательно: сначала — самого базового класса, затем его непосредственного потомка и т. д. Например:
class A
{
int m_a;
...
};
class B : A;
{
int m_b;
...
};
class C : B;
{
int m_c;
...
};
Объект класса C:
*-----------------* <-this
* Данные класса A *
*-----------------*
* Данные класса B *
*-----------------*
* Данные класса C *
*-----------------*
Любая функция в классах A, B или C точно "знает" адреса членов класса, с которыми работает:
m_a: this+0
m_b: this+4
m_c: this+8
Поэтому любой из этих функций можно смело передавать this, не прибавляя к нему смещение.
Теперь рассмотрим случай из примера Алекса Федотова.
class A
{
int m_a;
...
};
class B;
{
int m_b;
...
};
class C : A, B;
{
int m_c;
...
};
Объект класса C выглядит так же, как и раньше:
*-----------------* <-this
* Данные класса A *
*-----------------*
* Данные класса B *
*-----------------*
* Данные класса C *
*-----------------*
Но теперь функции класса B, которые понятия не имеют о существовании класса C, используют для доступа к члену m_b адрес this+0. Если вызвать функцию класса B для класса C, он будет работать с членом A::m_a по адресу this+0, как будто это B::m_b. Вот почему для обеспечения корректности программы приходится прибавлять смещение к указателю this при использовании функций класса B для объекта C. Поэтому в указателе на функцию для класса C появляется дополнительное поле — смещение. Для любых функций из классов A и C это смещение будет равно нулю, для функций класса B — четырём.
С уважением,
Александр
--
Я думал, ты огромный страшный Бажище,
А ты недоучка, крохотный Бажик...
Здравствуйте Alexander Shargin, вы писали:
AS>Мне кажется, нужно всё-таки внести окончательную ясность по данному вопросу. Иначе эта дискуссия с цитированием ассемблерных листингов собьёт с толку кого угодно. :)
Какой же вы политкорректный все-таки, даже не обвинили меня в подтасовке фактов, хотя на самом деле до этого было недалеко, поскольку пример я подбирал специально :)
AS>В большинстве случаев указатель на невиртуальную функцию-член всё-таки занимает 4 байта — только адрес функции. Дополнительные 4 байта — смещение — возникают только при множественном наследовании. При обычном наследовании смещение не нужно, так оно всегда равнялось бы нулю, и мы просто без нужды тратили бы память.
С другой стороны, можно сказать, что в общем случае множественного наследования указатель занимает 8 байт, а в частном случае одиночного наследования достаточно четырех. Это я не согласен с фразой "в большинстве случаев".
AS>Поясню, зачем вообще понадобилось это смещение.
Это разъяснение достойно помещения в FAQ. Можно я его сохраню для цитирования?
У меня закралось подозрение, что что-то здесь не так. А нельзя ли гипотетический пример привести в практический, да так чтобы в консольку выводился sizeof(...).
Здравствуйте VladD2, вы писали:
VD>Здравствуйте Alexander Shargin:
VD>У меня закралось подозрение, что что-то здесь не так. А нельзя ли гипотетический пример привести в практический, да так чтобы в консольку выводился sizeof(...).
Ну вы и извращенец, батенька :). Такими примерами можно запросто расшатать неокрепшую психику начинающих программистов.
VD> Что нужно сделать, чтобы он сработал не верно?
Это слегка модифицированный мой пример.
class CA
{
public:
CA() { m_a = 1; }
int A() { return т_a; }
protected:
int m_a;
};
class CB
{
public:
CB { m_b = 2;
int B() { return m_b; }
protected:
int m_b;
};
class CC : public CA, public CB
{
public:
CC { m_c = 3;
int C() { return 3; }
protected:
int m_c;
};
typedef int (CC::*CPtr)();
int
_tmain(
int argc,
_TCHAR * argv[]
)
{
printf("sizeof(CPtr) = %d\n", sizeof(CPtr));
CPtr p = CC::B;
CC c;
printf("c.B() returns %d", (c.*p)());
return 0;
}
Пожалуйста, замени (c.*p)() вызовом через свой универсальный указатель.
По-моему, Алескандр все очень наглядно объяснил и должно быть понятно, почему это не будет работать.
Здравствуйте Alexander Shargin, вы писали:
AS>В большинстве случаев указатель на невиртуальную функцию-член всё-таки занимает 4 байта — только адрес функции. Дополнительные 4 байта — смещение — возникают только при множественном наследовании. При обычном наследовании смещение не нужно, так оно всегда равнялось бы нулю, и мы просто без нужды тратили бы память.
AS>Поясню, зачем вообще понадобилось это смещение. Дело в том, что переменные-члены классов, образующих цепочку наследования, располагаются в памяти последовательно: сначала — самого базового класса, затем его непосредственного потомка и т. д. Например:
AS>Но теперь функции класса B, которые понятия не имеют о существовании класса C, используют для доступа к члену m_b адрес this+0. Если вызвать функцию класса B для класса C, он будет работать с членом A::m_a по адресу this+0, как будто это B::m_b. Вот почему для обеспечения корректности программы приходится прибавлять смещение к указателю this при использовании функций класса B для объекта C. Поэтому в указателе на функцию для класса C появляется дополнительное поле — смещение. Для любых функций из классов A и C это смещение будет равно нулю, для функций класса B — четырём.
Я тут покопался в MSDN'е и нашел интересную штуку к теме. Хочу поделиться размышлениями. Поправьте меня, если ошибся где-то.
Если говоришь
#pragma pointers_to_members(full_generality,single_inheritance)
то поинтер будет 4-хбайтовым, но сгернерится ошибка, если схема наследования множественная или виртуальная.
#pragma pointers_to_members(full_generality,multiple_inheritance)
то поинтер будет 8-мибайтовым, но всё-таки сгернерится ошибка, если схема наследования виртуальная.
#pragma pointers_to_members(full_generality,virtual_inheritance)
то поинтер будет 8-мибайтовым, ошибок не будет никогда.
Всё вроде как понятно — первые два типа наследования Вы подробно разъяснили, но как быть с виртуальным наследованием? Не будете ли Вы любезным просветить неосведомлённых вроде меня?
Здравствуйте Yarik, вы писали:
VD>>Интересно! А что произойдет если в GWL_USERDATA окна к которому Вы подключаетесь будет лежать какая то информация?
Y>А что там может лежать, если окно создаю я ?! На то оно и USERDADA, чтобы USER мог ложить туда свои DATA.
А какой смысл сабкласить окно если оно Ваше? Вы же имеете полный доступ к процедуре окна! Не проще ли вставить в неё вызов нузной функции.
Сабкласинг окон нужен в первую очередь для перехвата сообщений чужого окна и тут Ваш способ может привести к ошибке. Лучше закладывайте информацию в доп. свойства окна, а еще проще, эфективнее и надежнее использовать CWindowImpl<> из ATL. В ATL сабкласинг делаест очень красиво.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте Alex Fedotov, вы писали:
AF>Здравствуйте VladD2, вы писали:
VD>>Здравствуйте Alexander Shargin:
VD>>У меня закралось подозрение, что что-то здесь не так. А нельзя ли гипотетический пример привести в практический, да так чтобы в консольку выводился sizeof(...).
AF>http://rsdn.ru/forum/?action=message&gid=9&mid=3097
).
AF>Ну вы и извращенец, батенька :). Такими примерами можно запросто расшатать неокрепшую психику начинающих программистов.
Ну, я что ли придумал, что указатель на функцию нельзя привести к void*? Я извращался по совершенно конкретному поводу. Одному из наших программистов было поручено написать инициализацию нашего поп-ап-меню с картинками (из ascLib http://www.optim.ru/Software/rus/ascLib/ascLib.asp). Хотелось сделать её на подобии мапов (BEGIN_MSG_MAP ...) в ATL, но класс работы с меню помещался как локальная переменная основного класса (все усугублялось тем, что такой класс должен иметь возможность содержать несколько экземпляров меню). Основной класс должен обрабатывать сообщения о нажатии пунктов. Т.е. на лицо необходимость вызова функции по указателю из другого класса... Меня попросили помочь. Вот и пришлось выпендриваться. Мне проще пору строк на inline-asm-е написать чем бороться с недоработками языков и/или компиляторов.
AF>Пожалуйста, замени (c.*p)() вызовом через свой универсальный указатель.
На досуге попробую.
AF>По-моему, Алескандр все очень наглядно объяснил и должно быть понятно, почему это не будет работать.
Да не очень. Мне осталось непонятно зачем запоминать смещение в указателе, если оно и так известно. Приводи указатель перед вызовом и все.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
VD>>>Интересно! А что произойдет если в GWL_USERDATA окна к которому Вы подключаетесь будет лежать какая то информация?
Y>>А что там может лежать, если окно создаю я ?! На то оно и USERDADA, чтобы USER мог ложить туда свои DATA.
VD>А какой смысл сабкласить окно если оно Ваше? Вы же имеете полный доступ к процедуре окна! Не проще ли вставить в неё вызов нузной функции.
VD>Сабкласинг окон нужен в первую очередь для перехвата сообщений чужого окна и тут Ваш способ может привести к ошибке. Лучше закладывайте информацию в доп. свойства окна, а еще проще, эфективнее и надежнее использовать CWindowImpl<> из ATL. В ATL сабкласинг делаест очень красиво.
Я знаю, для чего нужен сабклассинг. Но ведь установка USERDATA не явсяется сабклассингом — просто с HWND связывается какой-л. LONG — и не более. А работа с Window Properties сдаётся мне грубой и утомительной. Если надо ассоциировать с окном кучу разных данных, проще объявить структуру и положить указатель на неё в USERDATA. Это проще и быстрее, чем играться со свойствами окна.
К вопросу о содержимом той самой USERDATA: (цитата из MSDN)
GWL_USERDATA — Sets the 32-bit value associated with the window. Each window has a corresponding 32-bit value intended for use by the application that created the window. This value is initially zero.
Т.е. для своих окон (в смысле создания, а не определения) можно смело юзать данную штуку. И не в коем случае ничего туда не пихать для окон, созданных другими процессами.
Для окон с определенным програмистом классом можно сделать ещё проще: при регистрации класса указать кол-во дополнительно выделяемых байт на окно/на класс и получать доступ к каждому LONG'у, из них составленному, с помощью Set/GetWindowLong и Set/GetClassLong. (так написано в MSDN)
Здравствуйте VladD2, вы писали:
AF>>По-моему, Алескандр все очень наглядно объяснил и должно быть понятно, почему это не будет работать.
VD>Да не очень. Мне осталось непонятно зачем запоминать смещение в указателе, если оно и так известно. Приводи указатель перед вызовом и все.
А оно как раз не известно. Есть определение:
typedef int (CC::*CPtr)();
Есть указатель на член класса
CPtr p;
Eсть объект класса
CC c;
Вызываем
(c.*p)()
Как компилятор узнает, что p указывает на функцию из класса B, а не из класса A?
Здравствуйте Alex Fedotov, вы писали:
AF>Здравствуйте VladD2, вы писали:
VD>>Да не очень. Мне осталось непонятно зачем запоминать смещение в указателе, если оно и так известно. Приводи указатель перед вызовом и все.
AF>А оно как раз не известно. Есть определение: AF>typedef int (CC::*CPtr)(); AF>Есть указатель на член класса AF>CPtr p; AF>Eсть объект класса AF>CC c; AF>Вызываем AF>(c.*p)()
AF>Как компилятор узнает, что p указывает на функцию из класса B, а не из класса A?
Ну, если он не дурной, то глянет реализацию класса CC, вычислит смещение класса B и ровно на нее изменит указатель c.
В моем случае надо было вызывать методы относящиеся к самому верхнему классу, т.е. к CC в данной терминалогии, так и к нему нельзя. По-моему это или недоработка (Страуса, MS ...), или моё неумение решать проблемы данного класса средствами C++. Если верено последнее предположение, то может кно просветил бы, а?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте Alex Fedotov, вы писали:
AF>Как компилятор узнает, что p указывает на функцию из класса B, а не из класса A?
Если функция не виртуальная, то ее адрес всегда фиксирован. Адресс функции — это всеголишь адресс ее первой инструкции.
А если так, то смешение может относиться только к сдвизке this (о чем собственно и коворил Шаргин). Структура всех классов известна компилятору и он может добавлять или вычитать смещение сам без доп информации. Так вот, мне и непонятно почему такие плевые операции компилятор не может расчитать сам?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Они же запоминают не то! У них получается универсальный указатель на один из членов класса. В дельфях (если мне не изменяет память) указатель выглядел так: первое двойное слово было this-ом (self) конкретного экземпляра, а второе уже указателем на функцию.
Похоже подход дельфей более жизнеспособный. Честно говоря, я даже задачи не могу себе, представить, которая нуждалась бы в C++ указателях на одну из функций конкретного класса.
Ну, да ладно... Я переработал свой код так, чтобы он использовал ваш пример. Мой код использует Delphi-йскую идеологию, поэтому мне нужно было только скорректировать адрес this-а. Просто при присвоении указателя на this я учел, что функция чей адрес запоминается относится к дочернему классу. Вот код:
class CA
{
public:
CA(){ m_a = 1; }
int A(){ return m_a; }
protected:
int m_a;
};
class CB
{
public:
CB(){ m_b = 7777; }
int B(){ return m_b; }
protected:
int m_b;
};
class CC : public CA, public CB
{
public:
CC(){ m_c = 3; }
int C(){ return 3; }
protected:
int m_c;
};
typedef int (CC::*CPtr)();
// >>> --- CUniversalFuncPtr ---------------------------------------
struct CUniversalFuncPtr
{
void * pF; // Указатель на функцию класса
void * pThis; // Указатель на this экземпляр класса
};
int SomeCaller2(CUniversalFuncPtr UniversalFuncPtr)
{
int iRetVal;
__asm
{
// Эмулируем thiscall
mov ecx, UniversalFuncPtr.pThis;
mov eax, UniversalFuncPtr.pF;
int main(int argc, char* argv[])
{
// Создаем переменную — подопытного кролика...
CC c;
// Поулучаем переменную на функцию некоторого класса
// совершенно неважно какой это класс. Главное что функция
// должна быть описанна как:
// void FuncName(int);
typedef int ( CC::*pmfnP)(void);
pmfnP pFunction = &CC::B;
printf("sizeof(pmfnP) = %d\n", sizeof(pmfnP));
// Закладываем указатель на объекта (this)
// нужное смещение вычислит компилятор...
UniversalFuncPtr.pThis = (void*)(CB*)&c;
// а можно и так:
//UniversalFuncPtr.pThis = (void*)((char*)&c + *((int*)&pFunction+1));
// в этом случае смещение берется из тех самых лишних четырех байт,
// (о которых говорил Шаргин) добавляемых компилятором к указателю.
Здравствуйте VladD2, вы писали:
VD>Кажись, дошло и до меня. :)
VD> // Закладываем указатель на объекта (this) VD> // нужное смещение вычислит компилятор... VD> UniversalFuncPtr.pThis = (void*)(CB*)&c;
Проблема с этим в том, что не всегда заранее известно, на фукцию какого из базовых классов указывает указатель:
typedef int (CC::*CPtr)();
void Function(CPtr p)
{
CC c;
(c.*p)();
}
Имеющейся здесь информации недостаточно, чтобы кастировать this к нужному базовому классу во время компиляции, поэтому это происходит во время выполнения, а нужное смещение передается вместе с указателем.
VD> // а можно и так: VD> //UniversalFuncPtr.pThis = (void*)((char*)&c + *((int*)&pFunction+1)); VD> // в этом случае смещение берется из тех самых лишних четырех байт, VD> // (о которых говорил Шаргин) добавляемых компилятором к указателю.
Именно так и работает компилятор. Только он делает это в момент вызова, поскольку в момент создания указателя еще неизвестно, к какому экземпляру класса мы будем применять этот указатель.
AS>Объект класса C выглядит так же, как и раньше: AS>*-----------------* <-this AS>* Данные класса A * AS>*-----------------* AS>* Данные класса B * AS>*-----------------* AS>* Данные класса C * AS>*-----------------*
Хм.. тогда в чём здесь дело
=======
// test002.cpp : Defines the entry point for the console application.
//
-------------------------------------------------------------------
Вызывает презедент к себе коров и говорит:
— Ну, что будем сдавать, молоко или мясо?
(с) Г. Явлинский TV6 — Герой дня (18.04.2002)
Re: Адрес метода
От:
Аноним
Дата:
19.07.01 07:46
Оценка:
Здравствуйте Vovchik, вы писали:
V>К куче существующих окон необходимо заменить WindowProc. V>Первое, что пришло в голову, создать класс CMyWnd, V>содержащий метод MyWndowProc и атрибут OldWindwProc. V>Одна универсальная функция неэффективна, т.к. единственное V>различие для вызовов из разных окон это значение hWnd? V>а по нему искать каждый раз OldWindwProc не хочется.
V>Вопрос 1: Как заполучить адрес метода из экземпляра класса для SetWindowLong? V>Вопрос 2: Как иначе организовать множество функций различающихся только V> значением атрибута OldWindwProc.
1. В MFC эта задача решена через глобальную таблицу экземпляров классов окон.
2. В ATL более красиво, без всяких-там SetWindowLong, — через thunk:
поищи в сорцах ATL (atlwin.h) все, что связанно с
struct _WndProcThunk
{
DWORD m_mov; // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)
DWORD m_this; //
BYTE m_jmp; // jmp WndProc
DWORD m_relproc; // relative jmp
};
Здравствуйте Alexander Shargin, вы писали:
AS>template<typename PTR> union PtrConvertor
Да. template ... union это круто. :) Я чесно говоря даже незнал что так можно. Век живи, век... все равно... :)
Ну, а как на счет замены для вызова функции на asm-е? Я как то пробовал заменить его на вызов в стиле C-декл. (в смысле, глобальной функцией описанной как стандартная сишная ф-я), но что-то у меня не вышло. :( На asm-е получилось проще. :)
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте Alex Fedotov, вы писали:
AF>Проблема с этим в том, что не всегда заранее известно, на фукцию какого из базовых классов указывает указатель:
AF>typedef int (CC::*CPtr)();
AF>void Function(CPtr p) AF>{ AF> CC c; AF> (c.*p)(); AF>}
AF>Имеющейся здесь информации недостаточно, чтобы кастировать this к нужному базовому классу во время компиляции, поэтому это происходит во время выполнения, а нужное смещение передается вместе с указателем.
Ну, во время присвоения информации хоть отбавляй. :) К тому же мне (и остальным программистам) надо решать конкретные задачи, а не гипотетические.
Вот Александр Шаргин дал красивую идею как убрать asm пир копировании указателя на функцию. Может поможете убрать оставшийся asm?
VD>> // а можно и так: VD>> //UniversalFuncPtr.pThis = (void*)((char*)&c + *((int*)&pFunction+1)); VD>> // в этом случае смещение берется из тех самых лишних четырех байт, VD>> // (о которых говорил Шаргин) добавляемых компилятором к указателю.
AF>Именно так и работает компилятор.
Гы-гы. Так я у него и подсмотрел. :)
AF>Только он делает это в момент вызова, поскольку в момент создания указателя еще неизвестно, к какому экземпляру класса мы будем применять этот указатель.
В момент создания указателя не известно, а в момент его инициализации (присвоения ему реального значения, см. пример), известно!
AF>Вроде разобрались?
А что толку? Вы вон сами мой код назвали "расшатывающим неокрепшую психику начинающих программистов". :) Если уж в языке есть недоработки (или кривые места), то конструктивней попробовать сообща решить возникающие, из-за этого, проблемы средствами того же языка!
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте VladD2, вы писали:
VD>Вот Александр Шаргин дал красивую идею как убрать asm пир копировании указателя на функцию. Может поможете убрать оставшийся asm?
А зачем? Все равно это как было шаманством, так шаманством и останется, полагающимся на реализацию конкретного компилятора.
VD>>> // а можно и так: VD>>> //UniversalFuncPtr.pThis = (void*)((char*)&c + *((int*)&pFunction+1)); VD>>> // в этом случае смещение берется из тех самых лишних четырех байт, VD>>> // (о которых говорил Шаргин) добавляемых компилятором к указателю.
AF>>Именно так и работает компилятор. VD>Гы-гы. Так я у него и подсмотрел. :)
AF>>Только он делает это в момент вызова, поскольку в момент создания указателя еще неизвестно, к какому экземпляру класса мы будем применять этот указатель.
VD>В момент создания указателя не известно, а в момент его инициализации (присвоения ему реального значения, см. пример), известно!
Нет, нет и еще раз нет, если мы говорим о языке C++. В момени инизиализации указателя
CPtr ptr = CC::B;
известен только класс, который будет вызываться, а экземпляр класса, может появиться много позже. Хорошо известный пример — message maps в MFC, которые хранят указатели на члены класса. А объект появляется гораздо позже, более того, их может быть несколько.
Твой же UniversalFuncPtr -- скорее аналог интерфейса, чем указателя на метод класса. Кстати, в реальной жизни, я пользуюсь именно интерфейсами, а не указателями на члены класса.
AF>>Вроде разобрались?
VD>А что толку? Вы вон сами мой код назвали "расшатывающим неокрепшую психику начинающих программистов". :) Если уж в языке есть недоработки (или кривые места), то конструктивней попробовать сообща решить возникающие, из-за этого, проблемы средствами того же языка!
Ну, программисты на некоторых других языках (не будем показывать пальцем), считают, что множественное наследование является кривым местом, давайте теперь его исправлять. Я не считаю вызовы функций-членов класса кривыми и я понимаю, почему они реализованы именно так, а не иначе.
За сим откланиваюсь (подустал я от этой дискуссии, лучше пойду что-нибудь полезное сделаю).