Еще раз прошу прощения у общественности. Но на самом деле мне было важно не приведение типа к войд*, А приведение указателей на функции с неизвестнвм количеством парметров к некой одному типу, чего я и добился. Я всякие терки по поводу размера адреса функции эт пожалуйста на ваше усмотрение.
Здравствуйте, Андрей Якубовский, Вы писали:
АЯ>Еще раз прошу прощения у общественности. Но на самом деле мне было важно не приведение типа к войд*, А приведение указателей на функции с неизвестнвм количеством парметров к некой одному типу, чего я и добился. Я всякие терки по поводу размера адреса функции эт пожалуйста на ваше усмотрение.
Андрей, заводя новые ники вы еще раз попадаете под несоблюдение правил форума, в частности под спор с модератором. Вам запрещено писать в форум. Извольте соблюдать. В противном случае будет заблокирован ваш IP адрес и отключена возможность читать форум.
Новый ник заблокирован.
Обсуждения — только через moderator@rsdn.ru
Здравствуйте, Аноним, Вы писали:
А>Обратите пожалуйста внимание на __stdcall. Я готов пообсуждать без эмоций в чем я не прав. Токо пжалуйста не надо стучать себя кулоком в грудь и говорить что тут полный бред. А>
Указатели на методы классов в С++ обладают рядом уникальных свойств. На них стандартом языка накладываются определенные требования, удовлетворить которым можно только путем помещения в указатель дополнительной информации, наряду с банальным адресом точки входа в метод. По этой причине, если на некоторой платформа размер адреса равен 32-м битам, то размер корректно реализованного указателя на метод класса всегда заведомо больше 32-х бит (64 бита, например). По этой причине в общем случае невозможно сконвертировать указатель на метод класса в указатель типа 'void*' без потери информации. Какая-то информация будет потеряна. И это приведет к тому, что корректно вызвать метод через такой указатель будет невозможно.
Дополнительная информация, хранимая в указателе на метод, предназначена для вычисления корректного значения указателя 'this' объекта при вызове метода. В условиях обычного одиночного наследования эта информация не нужна. Но вот в условиях множественного наследования (не говоря уже о виртуальном) эта информация играет существенную роль. Я уже приводил пример с множественным наследованием, который демонстрирует (на уровне твоего любимого асма, сгенерированного MSVC++ 6) наличие в указателе на метод класса дополнительной информации (размер указателя в этом примере будет 64 бита) и то, как эта информация используется в процессе вызова. Я уже писал об этом более подробно, можно почитать здесь
Спасибо, что ответели. Все что вы сказали является истинной, я об этом не спорю. Я привел конкретный пример который нужен мне в моей реализации, и который работает на 100%. Возможно в других реализациях он работать не будет, в частности я проверил действительно при перегруженных виртуальных функциях этот код не работает. Но мне это и не надо. А что касаемомо размера функции отмечу что правы как Вы так и я. Вы парвы с точки зрения синтаксиса С++, я прав в постановке вопроса, на который в ответах была приведена куча кода, не отвечающая моим требованиям. Веди изначально вопрос ставился в возможности приведения типов, а не в последующем вызове функции.
Здравствуйте, Аноним, Вы писали:
А>Обратите пожалуйста внимание на __stdcall. Я готов пообсуждать без эмоций в чем я не прав. Токо пжалуйста не надо стучать себя кулоком в грудь и говорить что тут полный бред. А>
Что же касается работоспособности это кода — я не знаю, чем Вы его компилировали, но у меня ни один компилятор не пропускает строчку
void* Fn = mthdFunction;
по очевидным причинам. Предположим, что Ваш компилятор это пропускает. Прекрасно. Тогда могу посоветовать Вам скомпилировать и выполнить вот такой небольшой пример
struct A {
int i;
A() : i(1) {}
void __stdcall print() { std::cout << i << std::endl; }
};
struct B {
int i;
B() : i(2) {}
void __stdcall print() { std::cout << i << std::endl; }
};
struct C : A, B {
int i;
C() : i(3) {}
};
void call(C* pc, void (__stdcall C::*pm)()) {
(pc->*pm)();
Method p(pc, pm);
p.Run(0);
}
int main() {
C c;
call(&c, &A::print);
call(&c, &B::print);
}
и посмотреть на результаты его работы (надеюсь я правильно воспользовался Вашим указателем внутри метода 'call'?). Предлагаю Вам сравнить то, что печатает "стандартный" вызов метода через указатель и то, что печатает вызов через Ваш "указатель".
Я все-таки заставил MSVC++ 6 SP6 компилировать вышеприведенную строчку и вот, к примеру, вывод, который поучается при использовании этого компилятора
1
1
2
1
Как видите, Ваши вызовы всегда печатали '1', в то время как второй "стандартный" вызов напечатал '2', как и должно быть. Надеюсь, это наведет Вас на размышления (все еще надеясь, что я правильно воспользовался Вашим указателем).
Если Вы получаете иные результаты или у Вас имеются какие-то другие вопросы — то, учитывая сложившейся ситуации в форуме, мы можем продолжить эту дискуссию мылом (при условии отсутствия эмоций, разумеется).
Здравствуйте, Аноним, Вы писали:
>>Буду рад услышать Ваш ответ. В чем моя ошибка.
Ошибка в том, что ты выдаешь очень частный случай за верное решение.
И еще и упорствуешь.
Это примерно так же, как и реализовывать функцию сложения двух int
таким вот образом:
int Sum(int a, int b)
{
return 4;
}
В частном случае Sum(2,2); функция работает замечательно и очень эффективно.
Но кому нужно такое решение?
Re[5]: Указатель на функцию
От:
Аноним
Дата:
22.12.04 10:46
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:
Это моя оплошность в публикации кода.
вместо
void* Fn = mthdFunction;
надо
Function Fn = mthdFunction;
все компилируется, готов выслать проект, запускается все без проблем.
Здравствуйте, Аноним, Вы писали:
А>Спасибо, что ответели. Все что вы сказали является истинной, я об этом не спорю. Я привел конкретный пример который нужен мне в моей реализации, и который работает на 100%. Возможно в других реализациях он работать не будет, в частности я проверил действительно при перегруженных виртуальных функциях этот код не работает. Но мне это и не надо. А что касаемомо размера функции отмечу что правы как Вы так и я. Вы парвы с точки зрения синтаксиса С++, я прав в постановке вопроса, на который в ответах была приведена куча кода, не отвечающая моим требованиям. Веди изначально вопрос ставился в возможности приведения типов, а не в последующем вызове функции.
А>Буду рад услышать Ваш ответ. В чем моя ошибка.
Для начала стоило бы разобраться с тем, как Вам удалось скомпилировать строчку
void* Fn = mthdFunction;
в Вашем коде. Не один из имеющихся у меня под рукой компиляторов эту строчку в таком виде не пропускает. Вот, например, что сообщает MSVC++ 6 SP 6
error C2440: 'initializing' : cannot convert from 'long (__stdcall Method::*)(void)' to 'void *'
There is no context in which this conversion is possible
Здравствуйте, Аноним, Вы писали:
А>Это моя оплошность в публикации кода.
А>вместо А>void* Fn = mthdFunction; А>надо А>Function Fn = mthdFunction;
А>все компилируется, готов выслать проект, запускается все без проблем.
Да, но в таком случае в Вашем коде вообще не будет приведения указателя на метод класса к типу 'void*'!!! А это, напомню, и было основным камнем преткновения. Зачем же Вы, спрашивается, сообщали, что у Вас в коде прекрасно работает такое приведение, если на самом деле его у Вас в коде нет совсем? Это во-первых.
А во-вторых, Ваш код по прежнему использует только 32 бита указателя на метод — собственно адрес точки входа в метод
...
__asm mov eax,Fn
__asm call eax
...
Этого недостаточно для того, чтобы, например, корректно выполнить приведенный в моем предыдущем сообщении пример. Не затруднит ли Вас попробовать скомпилировать и выполнить этот пример и сообщить сюда, какой-же результат Вы получили?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>>Век живи — век учись. На 32-хбитной платформе битовый размер легального указателя на метод класса всегда строго больше 32-х бит. Например, на платформе MSVC++ указатель на метод класса, корректно работающий в условиях множественного наследования имеет размер 64 бита. Это и есть та причина, по котрой вопросы приведения указателя на метод класса к типу 'void*' не заслуживают никакого серъезного рассмотрения.
К>Во! Точно! Я не мог вспомнить, в каких случаях получается 128-битный указатель... Виртуальное наследование + множественное наследование + виртуальные функции. Вуаля!
Можно поподробнее?
Что находится в каждых 32 битах? и как осуществляется вызов по этом 128 битному адресу?
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Здравствуйте, jazzer, Вы писали:
J>>Боюсь, что мы сейчас увидим решение с ..., thunks в стиле ATL или еще что-нть непереносимое.
АТ>Решение, как видишь, уже опубликовано и включает банальное силовое (и, разумеется, некорректное) приведение указателя на метод к типу 'void*'. Гора родила мышь. Как и следовало ожидать, автор решения наивно полагает, что указатель на метод класса состоит только из адреса точки входа в метод
да.
я ожидал непереносимости между разными платформами (что-нть в стиле анализа стека, понимания, что же перед нами за указатель, и т.д.), а тут непереносимость уже между по-разному спроектированными классами и по-разному объявленными функциями даже в рамках одной платформы.
Здравствуйте, DangerDen, Вы писали:
К>>Во! Точно! Я не мог вспомнить, в каких случаях получается 128-битный указатель... Виртуальное наследование + множественное наследование + виртуальные функции. Вуаля!
DD>Можно поподробнее? DD>Что находится в каждых 32 битах? и как осуществляется вызов по этом 128 битному адресу?
Это внутреннее дело компилятора, вообще-то. И, честно сказать, я плохо представляю устройство. Могу только предположить.
Множественное наследование: требуется корректировка this для приведения к базовому типу.
struct A { int x; void a(); };
struct B { int y; void b(); };
struct C : A, B { int z; void c(); }
typedef void (C::*CF)();
int main()
{
CF cf = &B::b;
C* pc = new C;
B* pb = pc; // явное приведение типа, с коррекцией адреса
pb->b(); // коррекция не нужна
pc->b(); // коррекция на стадии компиляции: неявное приведение к B*
(pc->*cf)(); // откуда взять коррекцию? - только из cf
}
В принципе, компилятор мог бы завести прокси-метод
struct C: A, B
{
int z;
void call_b() { b(); } // коррекция указателя - здесь
};
int main()
{
CF fb = &B::b; // компилятор подставит &C::call_b
}
Но дело в том, что этот шаг может быть опосредован:
С виртуальными функциями — аналогичная история. Необходимо различать методы, вызываемые статически и динамически.
struct A { int x; void a() {} };
struct B: A { int y; virtual void b() {} };
struct C: B { int z; virtual void b() {} };
typedef void (A::*FA)();
typedef void (B::*FB)();
typedef void (C::*FC)();
int main()
{
B* pb = new C;
FB fa = &A::a;
FB fb = &B::b; // заметьте! мы здесь ничего не знаем про C
pb->a(); // статический вызов
pb->b(); // динамический вызов C::b
(pb->*fa)(); // статический вызов
(pb->*fb)(); // динамический вызов - обращаемся к vfptr/vtbl
}
С виртуальным наследованием — аналогично множественному наследованию, но смещение добывается из подпольной виртуальной функции.
Размер указателя будет зависеть от того, как устроены базовые классы.
Про 16 байт я, возможно, напутал (во всяком случае, сейчас не могу родить комбинацию, в которой это было бы так). Но 12 байт — стопудов.
I>Для тех кто в танке Получить адресс функции метода класса одназначно можно, без всяких хаков. Стандартным синтакисом С++. Проверяно в VC7.1 код совместим работает как, в релиз так, и в дебаг версии с уникодом и без.
Как бывший танкист замечу, что только указатель на метод простого класса можно привести к void*, потому что они имеют одинаковый размер. Через union например, но это хак. А вот указатели сложных классов с множественным наследованием, виртуальными функциями имеют размер 8 или 12 байт. Соответственно, если старшие один-два дворда не равны нулю, то твой хак не будет работать.
Помоему где-то на этом форуме (если мне не изменяет память, то господин Кодт) говорилось, что указатель на данные и указатель на код могут быт разной ширины. Так интересно, какой размер быдет у void*?
Здравствуйте, Plague, Вы писали:
P>Помоему где-то на этом форуме (если мне не изменяет память, то господин Кодт) говорилось, что указатель на данные и указатель на код могут быт разной ширины. Так интересно, какой размер быдет у void*?
Дело в том, что код и данные обычно размещаются в разных местах, и необязательно одинаково адресуемых.
На линейке интеловских процессоров, начиная с i8086 (и далее, включая 16-, 24-, 32- и 64-битные) адрес состоит из пары селектор:смещение.
Если весь код или все данные умещаются в сегмент с одним селектором, то можно единожды загрузить его значение в соответствующий сегментный регистр, и далее оперировать только смещениями.
Указатель, содержащий смещение и "подразумевающий" селектора — называется ближним, near. Указатель, содержащий и селектор, и смещение — называется дальним, far.
Причём, для кода и для данных селекторы могут различаться. Отсюда возникли модели памяти
tiny (flat) — и код, и данные разделяют один и тот же селектор. Все указатели ближние.
small — указатели ближние, но код и данные — в разных сегментах.
compact — данные не умещаются в один сегмент, используются дальние указатели. sizeof(void*) > sizeof(void(*)()).
medium — код не умещается в один сегмент. sizeof(void*) < sizeof(void(*)()).
large — дальние указатели и кода, и данных.
Но и это ещё не всё! Рабочий диапазон значений смещения тоже может варьироваться. Скажем, для IA-64 с плоской моделью можно ограничиться 32-битными смещениями для кода (из расчёта, что его объём — менее 4ГБ) и 64-битными — для кода.
Здравствуйте, Кодёнок, Вы писали:
I>>Для тех кто в танке Получить адресс функции метода класса одназначно можно, без всяких хаков. Стандартным синтакисом С++. Проверяно в VC7.1 код совместим работает как, в релиз так, и в дебаг версии с уникодом и без.
Кё>Как бывший танкист замечу, что только указатель на метод простого класса можно привести к void*, потому что они имеют одинаковый размер.
Нет, в компияоторе, который корректно реализует функциональность указателей на методы классов указатель на метод является "составным" и имеет по этой причне размер, больший чем 'void*'.
Кё>Через union например, но это хак. А вот указатели сложных классов с множественным наследованием, виртуальными функциями имеют размер 8 или 12 байт. Соответственно, если старшие один-два дворда не равны нулю, то твой хак не будет работать.
Но при этом надо заметить, что язык С++ позволяет использовать указатели "базовых" классов (путь они будут "простыми", т.е. без множественных предков) для указания на методы производных классов (пусть там появилось множественное наследование). По этой причине компиляторы всегда вынуждены использовать большие указатели на методы классов. Компиляторы, которые используют 32-хбитные указатели для указания на методы "простых" классов, работают с такими указателями некорректно. Примером является MSVC++, который в режиме "Best-case alwys" действиельно использует 32-битные указатели для методов "простых" классов. Не составляет никакого труда привести пример кода, который из-за этого будет работать некорректно. Для того, чтобы заставить MSVC++ работать с указателями корректно, надо лезть в установки компиляции и выставлять режим 'Any class' (или как его там). При этом указатели на методы сразу вырастут в размере , независимо от того, есть там множественное наследование или нет.
Best regards,
Андрей Тарасевич
Re: Указатель на функцию
От:
Аноним
Дата:
22.12.04 17:41
Оценка:
А немогли бы вы ответить на вопрос в топике (Начнем все сначала. Указатель на функцию) в чем там проблемы.