Получить смещение из указателя на член

133
11 декабря 2018, 02:50

Есть указатель на член, нужно получить числовое представление этого адреса, иными словами смещение. В коде вот так:

struct Foo
{
   int Bar;
};
auto pointer_to_member = &Foo::Bar;
auto  offset_of_member = reinterpret_cast<uintptr_t>(pointer_to_member); // ошибка

Мой VisualC++ пишет ошибку:

error C2440: reinterpret_cast cannot convert int Foo::Bar * to uintptr_t

Вопросы. Это что не по стандарту? И как получить смещение без offsetof так как в наличии уже готовый указатель на член?

Answer 1

MSVC прав, так делать нельзя.

Через offsetof тоже не получится, потому что он работает только с именами полей, а не с указателями на них.

На MSVC может сработать offsetof(Foo, *pointer_to_member) из-за особенностей реализации этого самого offsetof. Но это не сооветствует стандарту и не работает как минимум на GCC.

Единственный полностью стандартный способ (насколько я знаю) - это создать объект, применить к нему указатель на поле, и вычесть из адреса поля адрес объекта:

Foo foo;
std::cout << (char *)&(foo.*pointer_to_member) - (char*)&foo;

Более удобный вариант - это сделать type-punning из указателя на поле в uintptr_t через memcpy():

static_assert(sizeof(pointer_to_member) == sizeof(uintptr_t));
uintptr_t offset;
std::memcpy(&offset, &pointer_to_member, sizeof offset);
std::cout << offset;

Стандарт не гарантирует (насколько я знаю, опять же), что указатель на поле хранится как смещение, но это должно сработать на всех основных компиляторах.

Если под рукой нет уже готовых объектов Foo, я бы использовал именно этот способ.

Не стоит пробовать reinterpret_cast<uintptr_t &>(pointer_to_member), или type-punning через union. И то, и другое - неопределенное поведение из-за нарушения strict aliasing.

Вот еще один способ. Выглядит удобно, но является неопределенным поведением из-за доступа по невалидному указателю.
(Хотя дефолтная реализация offsetof в MSVC использует похожий трюк.)

std::cout << (uintptr_t)&((Foo *)(0)->*pointer_to_member);
Answer 2

Вашим способом нельзя. Открываем документацию на offsetof и читаем

offsetof cannot be implemented in standard C++ and requires compiler support

В gcc она сделана так

/* Offset of member MEMBER in a struct of type TYPE. */
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
Answer 3

Это что не по стандарту?

Да, это преобразование не по стандарту. Для указателей на член операцию преобразования к целому не определена. По факту указатель на член не является «указателем» в классическом понимании слова.

И как получить смещение без offsetof так как в наличии уже готовый указатель на член?

Легально — ни как. Внутреннее устройство указателя на член определяется реализацией.

Не легально на большинстве систем можно сделать что-то такое, но это UB:

*(off_t*) &pointer_to_member
READ ALSO
Настройка отладчика под macos в QtCreator

Настройка отладчика под macos в QtCreator

Ранее пользовался Linux/Ubuntu и не было ни каких проблем с отладкой (использовался gcc + gdb)Сейчас перешел на MacOS и появилась большая проблема

123
Ошибка при выполнении функции во время работы JavaFX

Ошибка при выполнении функции во время работы JavaFX

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

194
Изменение размера шрифта в listview

Изменение размера шрифта в listview

Есть приложение с таблицей Не обновляется БД в андроид приложении, необходимо по нажатию кнопки менять размер шрифтаВ activity_main

135