LINUX.ORG.RU

Короткий вопрос по С++


0

0

Как преобразовать указатель на метод класса в неопределенный указатель? Необходимо получить 'чистый' адрес метода, чтобы преобразовать его в число, и не спрашивайте зачем мне это нужно. :)

★★★★★

Любую конверсию можно осуществить через union {}

Absurd ★★★
()

Указатель на метод класса действует применительно к экземпляру класса и может быть просто индексом в таблице виртуальных методов.
То есть там 2 числа:
1) Адрес экземпляра. В качестве this передаётся в неявном параметре методов класса)
2) Адрес невиртуальной функции или индекс в таблице для виртуальной функции.

anonymous
()

Для вызова метода класса из какой-то функции (например функций обратного вызова) надо передавать 2 параметра: адрес экземпляра класса, адрес самой функции.

anonymous
()
Ответ на: комментарий от anonymous

Я знаю это. Видимо, все придется писать с расчетом на рантайм, но хоть бы и так - как это сделать? Не верю ведь, что нельзя.

jcd ★★★★★
() автор топика
Ответ на: комментарий от jcd

Стандарт C++ в 5.2.10/9 говорит:

An rvalue of type “pointer to member of X of type T1” can be explicitly converted to an rvalue of type “pointer to 10 member of Y of type T2” if T1 and T2 are both function types or both object types. The null member pointer value (4.11) is converted to the null member pointer value of the destination type. The result of this conversion is unspecified, except in the following cases:

— converting an rvalue of type “pointer to member function” to a different pointer to member function type and back to its original type yields the original pointer to member value.

— converting an rvalue of type “pointer to data member of X of type T1” to the type “pointer to data member of Y of type T2 (where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer to member value.

Указатель на один метод можно только лишь преобразовать к указателю на другой метод и результат будет, как замечено выше, «unspecified».

anonymous
()
template <typename T>
PointerId(T function)
{
    unsigned int parts(sizeof(T) / sizeof(unsigned long));

    for(unsigned int i(0); i < parts; ++i)
    {
        function_.push_back( *(reinterpret_cast<unsigned int *>(&function) + i) );
    }
}

template <typename T1, typename T2>
PointerId(T1 function, T2 object)
{
    unsigned int parts1(sizeof(T1) / sizeof(unsigned long));

    for(unsigned int i(0); i < parts1; ++i)
    {
        function_.push_back( *(reinterpret_cast<unsigned int *>(&function) + i) );
    }

    unsigned int parts2(sizeof(T2) / sizeof(unsigned long));

    for(unsigned int i(0); i < parts2; ++i)
    {
        object_.push_back( *(reinterpret_cast<unsigned int *>(&object) + i) );
    }
}

когда я был молодой и глупый, я делал как-то так :)

jtootf ★★★★★
()
#include <iostream>
#include <iomanip>
using namespace std;

class C
{
	int m;
public:	
	void a1(){cout << "a1" << m << endl; }
	void a2(){cout << "a2" << m << endl; }
	virtual void b(){}
};

void func(){cout << "f" << endl;}

union u
{
	uint64_t a[2];
	void (C::*ptr)();	
};

int main (int, char**)
{
	C x;
	cout << "Функция простая " << sizeof(&func) << "\nфункция класса " << sizeof(&C::a1) <<  "\nфункция класса виртуальная " << sizeof(&C::b) << "\n";
	cout << setfill('0');
	cout << "Указатель на функцию простую: "  << reinterpret_cast<void*>(func) << "\n";
	u u1;	
	u1.ptr = &C::a1;	
	cout << "Указатель на функцию a1 класса: " << hex << setw(8) << u1.a[1] << setw(8) << u1.a[0] << "\n";
	u1.ptr = &C::a2;	
	cout << "Указатель на функцию a2 класса: " << hex << setw(8) << u1.a[1] << setw(8) << u1.a[0] << "\n";
	u1.ptr = &C::b;
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(8) << u1.a[1] << setw(8)  << u1.a[0] << "\n";
	cout << flush;
	return 0;
}

вывод программы

Функция простая 8
функция класса 16
функция класса виртуальная 16
Указатель на функцию простую: 0x400bdc
Указатель на функцию a1 класса: 0000000000401048
Указатель на функцию a2 класса: 0000000000401006
Указатель на функцию класса виртуальную: 0000000000000001

Отсюда очень похоже, что компилятор GCC для невиртуальных функций в их указателе хранит действительно указатель на функцию класса.

anonymous
()
#include <iostream> 
#include <iomanip> 
using namespace std; 

class C 
{ 		
	public:    
		int64_t m; 
		void a1(){cout << "a1" << m << endl; } 
		void a2(){cout << "a2" << m << endl; } 
		virtual void b1(){m=1;}
		virtual void b2(){m=2;} 
		virtual void b3(){m=3;} 
		virtual void b4(){m=4;}
		virtual void b5(){m=5;} 
}; 

void func(){cout << "f" << endl;} 

union u 
{ 
	uint64_t a[2]; 
	void (C::*ptr)();    
}; 

int main (int, char**) 
{ 
	C x; 
	cout << "Размер класса " << sizeof(C)  << "\nФункция простая " << sizeof(&func) << "\nфункция класса " << sizeof(&C::a1) <<  "\nфункция класса виртуальная " << sizeof(&C::b1) << "\n"; 
	cout << setfill('0'); 
	cout << "Указатель на функцию простую: "  << reinterpret_cast<void*>(func) << "\n"; 
	u u1;    
	u1.ptr = &C::a1;    
	cout << "Указатель на функцию a1 класса: " << hex << setw(16) << u1.a[1] << setw(16) << u1.a[0] << "\n"; 
	u1.ptr = &C::a2;    
	cout << "Указатель на функцию a2 класса: " << hex << setw(16) << u1.a[1] << setw(16) << u1.a[0] << "\n"; 
	u1.ptr = &C::b1; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n";
	u1.ptr = &C::b2; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n";
	u1.ptr = &C::b3; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n"; 
	u1.ptr = &C::b4; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n"; 
	u1.ptr = &C::b5; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n"; 
	
	x.m = 0x1010101010101010;	
	cout << "Содержимое класса:: " <<  hex << setw(16) << *((uint64_t*)&x + 1) << " " << setw(16)  << *((uint64_t*)&x) << "\n"; 	
	
	cout << flush; 
	return 0; 
}
Размер класса 16
Функция простая 8
функция класса 16
функция класса виртуальная 16
Указатель на функцию простую: 0x400bdc
Указатель на функцию a1 класса: 00000000000000000000000000401362
Указатель на функцию a2 класса: 0000000000000000000000000040131e
Указатель на функцию класса виртуальную: 00000000000000000000000000000001
Указатель на функцию класса виртуальную: 00000000000000000000000000000009
Указатель на функцию класса виртуальную: 00000000000000000000000000000011
Указатель на функцию класса виртуальную: 00000000000000000000000000000019
Указатель на функцию класса виртуальную: 00000000000000000000000000000021
Содержимое класса:: 1010101010101010 00000000004016b0

Видно, что для указателей на виртуальные функции хранится индекс в таблице вируальных методов, сама таблица хранится по нулевому адресу у класса, занимая первые 8 байт.

anonymous
()
Ответ на: комментарий от anonymous

конечно же не таблица там хранится, а указатель на таблицу

anonymous
()

Добавляя в конце такой код

void** p_virtual = (void**)(*((uint64_t*)&x));	
	cout << "Содержимое таблицы:\n" ;	
	for (int i = 0; i < 5; ++i)
	{
		cout << i << " " << *(p_virtual + i) <<"\n";
	}
получаем теперь в дополнение к ранее полученным адресам не виртуальных функций (0x401420 и 0x4013dc) также и список адресов виртуальных функций:
Содержимое таблицы:
0 0x401358
1 0x40136e
2 0x401384
3 0x40139a
4 0x4013b0

anonymous
()

Получив все адреса, можно теперь вызывать функции класса как обычные функции с явным параметром this.

#include <iostream> 
#include <iomanip> 
using namespace std; 

class C 
{ 		
	public:    
		int64_t m; 
		void a1(){m++; cout << "пишем из a1 и m==" << m << endl; }
		void a2(){m++; cout << "пишем из a2 и m==" << m << endl; }
		virtual void b1(){m++; cout << "пишем из b1 и m==" << m << endl; }
		virtual void b2(){m++; cout << "пишем из b2 и m==" << m << endl; } 
		virtual void b3(){m++; cout << "пишем из b3 и m==" << m << endl; } 
		virtual void b4(){m++; cout << "пишем из b4 и m==" << m << endl; }
		virtual void b5(){m++; cout << "пишем из b5 и m==" << m << endl; } 
}; 

void func(){cout << "f" << endl;} 

union u 
{ 
	uint64_t a[2]; 
	void (C::*ptr)();    
}; 

int main (int, char**) 
{ 
	C x; 
	cout << "Размер класса " << sizeof(C)  << "\nФункция простая " << sizeof(&func) << "\nфункция класса " << sizeof(&C::a1) <<  "\nфункция класса виртуальная " << sizeof(&C::b1) << "\n"; 
	cout << setfill('0'); 
	cout << "Указатель на функцию простую: "  << reinterpret_cast<void*>(func) << "\n"; 
	u u1;    
	u1.ptr = &C::a1;    
	cout << "Указатель на функцию a1 класса: " << hex << setw(16) << u1.a[1] << setw(16) << u1.a[0] << "\n"; 
	u1.ptr = &C::a2;    
	cout << "Указатель на функцию a2 класса: " << hex << setw(16) << u1.a[1] << setw(16) << u1.a[0] << "\n"; 
	u1.ptr = &C::b1; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n";
	u1.ptr = &C::b2; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n";
	u1.ptr = &C::b3; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n"; 
	u1.ptr = &C::b4; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n"; 
	u1.ptr = &C::b5; 
	cout << "Указатель на функцию класса виртуальную: " <<  hex << setw(16) << u1.a[1] << setw(16)  << u1.a[0] << "\n"; 
	
	x.m = 0x1010101010101010;	
	cout << "Содержимое класса:: " <<  hex << setw(16) << *((uint64_t*)&x + 1) << " " << setw(16)  << *((uint64_t*)&x) << "\n";
	
	void** p_virtual = (void**)(*((uint64_t*)&x));	
	cout << "Содержимое таблицы:\n" ;	
	for (int i = 0; i < 5; ++i)
	{
		cout << i << " " << *(p_virtual + i) <<"\n";
	}
	
	cout << " А теперь вызовы функций класса без использования функций класса,\n"
	 "но через указатель на обычную функцию с явным параметром this. \n";

	typedef void (*some)(void*);
	some some_ptr;
		
	u1.ptr = &C::a1;
	some_ptr = (some)(u1.a[0]);
	some_ptr(&x);
	
	u1.ptr = &C::a2;
	some_ptr = (some)(u1.a[0]);
	some_ptr(&x);
	
		
	for (int i = 0; i < 5; ++i)
	{
		some_ptr = (some)(*(p_virtual + i));
		some_ptr(&x);
	}
		
		
	cout << flush; 
	return 0; 
}

anonymous
()

Результат работы программы:

Размер класса 16
Функция простая 8
функция класса 16
функция класса виртуальная 16
Указатель на функцию простую: 0x400c2c
Указатель на функцию a1 класса: 00000000000000000000000000401602
Указатель на функцию a2 класса: 000000000000000000000000004015aa
Указатель на функцию класса виртуальную: 00000000000000000000000000000001
Указатель на функцию класса виртуальную: 00000000000000000000000000000009
Указатель на функцию класса виртуальную: 00000000000000000000000000000011
Указатель на функцию класса виртуальную: 00000000000000000000000000000019
Указатель на функцию класса виртуальную: 00000000000000000000000000000021
Содержимое класса:: 1010101010101010 0000000000401b10
Содержимое таблицы:
0 0x401552
1 0x4014fa
2 0x4014a2
3 0x40144a
4 0x4013f2
 А теперь вызовы функций класса без использования функций класса,
но через указатель на обычную функцию с явным параметром this. 
пишем из a1 и m==1010101010101011
пишем из a2 и m==1010101010101012
пишем из b1 и m==1010101010101013
пишем из b2 и m==1010101010101014
пишем из b3 и m==1010101010101015
пишем из b4 и m==1010101010101016
пишем из b5 и m==1010101010101017

anonymous
()
Ответ на: комментарий от anonymous

Вам, милейший, преподавателем работать нужно. :-) Большое спасибо за помощь!

jcd ★★★★★
() автор топика

учтите размер

учтите что размер указателя на метод существенно больше чем просто указателя и вообще говоря зависит от опций компиляции (по крайней мере для icc). Так что бинарной совместимости с уже скомпиленной либой можно и не получить. Еще один нюанс - размер указателей на метод может отличаться между собой :) так что ориентироваться лучше на максимальный.

Так что никаких кастов, и наверное стоит повторить тк union и прочие скрытые касты тоже нельзя.

а так обычная задачка на написание хэш функции от структуры из нескольких полей. И не забудьте про написанное выше by jtootf «молодой и глупый» :)

зы анонимусы лазящие в vmt грязными руками жгут как обычно принято на лоре.

sleepy
()

Вообще говоря, во вменяемом виде - никак.
Microsoft, например, для обхода этого придумала COM.

Love5an
()

Вдогонку ещё разберитесь, как работают виртуальные функции при использовании виртуального множественного наследования (class A : virtual B, virtual C { ... }; ). Там может быть немножко сложнее чем индекс в vtable.

Legioner ★★★★★
()

Про то что указатели на методы могут быть разного размера уже написали... можно так решить эту проблему:

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

template <typename T, void (T::*ptr)()>
void stub(T* object) {
    (object->*ptr)();
}

int main() {
    typedef void (*OverloadHelper)(Foo*);
    void* ptr = reinterpret_cast<void*>((OverloadHelper) &stub<Foo, &Foo::bar>);
    return 0;
}
nozh
()

sizeof (this) >= sizeof (void*)

В общем случае размер this может быть больше void*. С очень большой вероятностью будет совпадать если: включена оптимизация, класс не имеет виртуальных функций, нет множественного и виртуального наследования. Всё остальное очень сильно зависит от реализации и конкретной ситуации.

ly
()
Ответ на: sizeof (this) >= sizeof (void*) от ly

Чё-то Вы какую-то фигню сказали. sizeof(this) всегда равен sizeof(void*). this хранит адрес экземпляра объекта, являясь указателем на данные таким же, как и int* и все остальные.

anonymous
()
Ответ на: sizeof (this) >= sizeof (void*) от ly

Стандарт С++

9.3.2 The this pointer [class.this] In the body of a non-static (9.3) member function, the keyword this is a non-lvalue expression whose value is the 1 address of the object for which the function is called. The type of this in a member function of a class X is X*. If the member function is declared const, the type of this is const X*, if the member function is declared volatile, the type of this is volatile X*, and if the member function is declared const volatile, the type of this is const volatile X*.

Ключевая фраза The type of this in a member function of a class X is X*. Как sizeof(MyClass*) может быть не равен sizeof(void*)?

anonymous
()
Ответ на: комментарий от anonymous

> Как sizeof(MyClass*) может быть не равен sizeof(void*)?

ну его можно сконвертировать в void* и обратно. С большой долей уверенности можно тогда сказать что sizeof(MyClass*) <= sizeof(void*)

Также вроде бы гарантируется что указатели на любую структуру и класс имеют одинаковый размер.

Но я не улавливаю почему должно быть строгое равенство sizeof(MyClass*) == sizeof(void*)

dilmah ★★★★★
()
Ответ на: комментарий от dilmah

3.9.2/3

A valid value of an object pointer type represents either the address of a byte in memory (1.7) or a null pointer (4.10). If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained.

Задача указателя - хранить адрес байта в памяти.

3.9.2/4

Objects of cv-qualified (3.9.3) or cv-unqualified type void* (pointer to void), can be used to point to objects of unknown type. A void* shall be able to hold any object pointer. A cv-qualified or cv-unqualified (3.9.3) void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified char*.

Таким образом реализация указателя на void должна иметь такое представление и выравнивание как указатель на char.

Отсюда следует, что sizeof(void*)==sizeof(char*). Так требует стандарт С++.

anonymous
()
Ответ на: комментарий от anonymous

Это в новых компиляторах почти всегда так, но вообще this может быть «двойным», т.е. парой указателей. Например, кроме указателя на данные, может быть отдельный указатель на VMT или на экземпляр виртуального предка.

ly
()
Ответ на: комментарий от ly

>1) The type of this in a member function of a class X is X*
значит this это указатель на класс

2) void* shall be able to hold any object pointer.

значит void* должен уметь хранить и this.

3) A cv-qualified or cv-unqualified (3.9.3) void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified char*.

значит void* имеет такое же устройство, как и char*.

Из этих 3 пунктов следует, что char* может хранить this и наоборот без потери информации об адресе. А «двойной» указатель это не указатель, а указатель на массив указателей.

anonymous
()
Ответ на: комментарий от anonymous

Я сам видел (парился с оптимизацией и отладкой) «двойной this» в бытность DOS (Turbo C++, Borland C++, MS Quick C). Но уже уверенно не смогу сказать в каком именно компиляторе это было. Скорее всего в борландовских, кажется это было замечено при допиливании TurboVision.

Сейчас на gcc 4.4 воспроизвести ситуацию не могу. Думаю все стало лучше после стандартизации языка (включая некоторые аспекты реализации) и ABI.

ly
()
Ответ на: комментарий от anonymous

> Отсюда следует, что sizeof(void*)==sizeof(char*). Так требует стандарт С++.

да, для char* есть такая оговорка, потому что исторически в С роль void* сперва исполнял char*. Только потом ввели void*

Почему указатель на другой тип не может быть меньше чем void* мне не понятно.

dilmah ★★★★★
()
Ответ на: комментарий от dilmah

Ясным английским языком в стандарте написано:

«void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified char*»

anonymous
()
Ответ на: комментарий от dilmah

Не менее ясный английский язык в другом сообщении:

A valid value of an object pointer type represents either the address of a byte in memory (1.7) or a null pointer (4.10).

В указателе хранится адрес БАЙТА в памяти, независмо от того, какой там тип в памяти по адресу этого байта.

Неужели непонятно до сих пор?

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.