2015-11-23 2 views
17

Предположим, что есть структураПолучить указатель на объект от указателя на какой-то член

struct Thing { 
    int a; 
    bool b; 
}; 

и я получаю указатель на член b этой структуры, скажем, в качестве параметра некоторой функции:

void some_function (bool * ptr) { 
    Thing * thing = /* ?? */; 
} 

Как получить указатель на содержащий объект? Самое главное: не нарушая какое-либо правило в стандарте, то есть я хочу, чтобы стандартное поведение определялось, а не неопределено, и не было определено поведение реализации.

Как сторона примечание: Я знаю, что это обходит тип безопасности.

+1

См. 'Offsetof'. – deviantfan

+2

сложно сместить указатель 'Thing' с адреса' bool'. Почему бы вам не передать адрес Thing/указатель на вашу функцию? – Stefan

+1

@Stefan Предполагается, что это часть распределителя: у него есть узлы с бухгалтерией и данными, я раздаю указатель на данные о распределении и верну его обратно на освобождение. –

ответ

15

Если вы уверены, что указатель действительно указывает на члена b в структуре, как если бы кто-то сделал

Thing t; 
some_function(&t.b); 

Тогда вы должны быть в состоянии использовать offsetof макрос, чтобы получить указатель на структуру :

std::size_t offset = offsetof(Thing, b); 
Thing* thing = reinterpret_cast<Thing*>(reinterpret_cast<int8_t*>(ptr) - offset); 

Обратите внимание, что если указатель ptr фактически не указывают на Thing::b члена, то приведенный выше код приведет к непредсказуемому поведению, если вы используете указатель thing.

+1

Хм ... но что, если 'sizeof (int8_t)! = Sizeof (char)'? – deviantfan

+1

@deviantfan Учитывая, что 'sizeof (char)' определяется в спецификации на * always *, приводит к '1', что не будет большой проблемой. :) И это фактическая точка использования 'int8_t', она определена как байт, который' char' может и не быть. –

+3

AFAIK 'char *' полностью безопасен и каноничен для такой арифметики указателей. –

4
void some_function (bool * ptr) { 
    Thing * thing = (Thing*)(((char*)ptr) - offsetof(Thing,b)); 
} 

Я думаю нет UB.

3
X* get_ptr(bool* b){ 
    static typename std::aligned_storage<sizeof(X),alignof(X)>::type buffer; 

    X* p=static_cast<X*>(static_cast<void*>(&buffer)); 
    ptrdiff_t const offset=static_cast<char*>(static_cast<void*>(&p->b))-static_cast<char*>(static_cast<void*>(&buffer)); 
    return static_cast<X*>(static_cast<void*>(static_cast<char*>(static_cast<void*>(b))-offset)); 
} 

Во-первых, мы создаем некоторое статическое хранилище, которое может провести X. Затем мы получаем адрес объекта X, который может существовать в буфере, и адрес элемента b этого объекта.

Кастинг назад char*, мы можем таким образом получить смещение bool в буфере, который мы можем использовать, чтобы настроить указатель на реальный bool обратно к указателю вмещающего X.

+0

Вы хотите объявить 'buffer' как' static typename std :: aligned_storage :: type'? Кроме того, есть ли причина для 'static_cast' через' void * 'вместо просто' reinterpret_cast' непосредственно для 'char *' и 'X *'? –

+0

Да, хорошо пятнистый. Мне не нравится 'reinterpret_cast', поскольку отображение плохо указано. 'static_cast' имеет определенное сопоставление. –

+0

Я предполагаю, что это имеет смысл вообще, хотя 'reinterpret_cast' для указателей хорошо определен. Мне не нравится, что это резервирует лишнее пространство, но в остальном это кажется хорошим. Я очень немного обеспокоен тем, что смещение не совпадает с этим «X» и одним аргументом, но если компилятор делает это (я не могу найти никаких доказательств, которые он не может сделать, для нестандартной компоновки , но я сомневаюсь, что все это делают), я не уверен, что есть способ обойти это. –

1

Мое предложение получено из ответа @Rod в Offset from member pointer without temporary instance, а аналогичное @ 0xbadf00d - в Offset of pointer to member.

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

Я не практикующий C++, так сожалею о краткости.

#include <iostream> 
#include <cstddef> 

using namespace std; 

struct Thing { 
    int a; 
    bool b; 
}; 

template<class T, typename U> 
std::ptrdiff_t member_offset(U T::* mem) 
{ 
    return 
    (&reinterpret_cast<const char&>( 
     reinterpret_cast<const T*>(1)->*mem) 
     - reinterpret_cast<const char*>(1)  ); 
} 

template<class T, typename U> 
T* get_T_from_data_member_pointer (U * ptr, U T::*pU) { 
    return reinterpret_cast<T*> (
     reinterpret_cast<char*>(ptr) 
    - member_offset(pU)); 
} 

int main() 
{ 

    Thing thing; 
    thing.b = false; 

    bool * ptr = &thing.b; 
    bool Thing::*pb = &Thing::b; 

    std::cout << "Thing object address accessed from Thing test object lvalue; value is: " 
     << &thing << "!\n";  
    std::cout << "Thing object address derived from pointer to class member; value is: " 
     << get_T_from_data_member_pointer(ptr, &Thing::b) << "!\n";  
} 
+0

Использует '1' как указатель на самом деле совместимый со стандартами или может иметь проблемы с выравниванием или что-то еще? Я действительно думаю, что вам не разрешено преобразовывать любые 'int' в указатель, если это значение не было рассчитано из ограниченного набора операций * из * указателя изначально, хотя может быть, что вам просто не разрешено разыгрывать это. Хотя я много раз думал о том, что я хочу, чтобы C++ предоставлял по умолчанию 'T * operator- (U *, U T :: *)' для всех типов 'T' и' U'. –

+0

Или, по крайней мере, этот 'gcc' предоставил это расширение, так как я уверен, что он фактически хранит переменные-указатели-данные-данные как' ptrdiff_t' (https://refspecs.linuxfoundation.org/cxxabi-1.86 .html). –

+0

Я не думаю, что это было бы уступчиво, как большинство проблем «reainterpret_cast», если я правильно помню из своего старого исследования C++ 98. Но преобразование сложно, потому что это всего лишь способ вывести смещение, учитывая, что указатель элемента данных класса реализуется со смещением/смещением. Это просто дает базу для вычисления арифметики указателя, ничего не разыменовывает. Исправьте меня, если я ошибаюсь. Ответ @Rod указывает на то, как этот конкретный свернутый синтаксис (ссылка на char, сначала, а затем на получение адреса) вызван предупреждениями компилятора в строке ваших наблюдений. – rfb

Смежные вопросы