Это действительный код ANSI C ++? Попытка сгенерировать смещения членов структуры во время компиляции

Возможные дубликаты:
Вызывает ли макрос offsetof из ‹stddef.h› неопределенное поведение?
разыменование нулевого указателя

    #define _OFFS_OF_MEMBER(p_type, p_member) (size_t)&(((p_type *)NULL)->p_member)

    struct a 
    {
             int a, b;
    };

    size_t l = _OFFS_OF_MEMBER(struct a, b);

У меня был небольшой чат / беседа с некоторыми другими пользователями, и один из них сказал, что это разыменование и доступ к адресному пространству рядом с адресом NULL. Я сказал: взятие адреса члена не приведет к доступу, касанию или чтению значения этого члена. По стандарту полностью безопасен.

    struct a* p = NULL;
    size_t offset = &p->b; // this may NOT touch b, it is not dereferencing
    // p->b = 0; // now, we are dereferincing: acccess violation time!

Всегда ли это безопасный способ вычисления смещения, или компиляторы могут разыменовать и испортить память рядом с адресом NULL в соответствии со стандартами?

Я знаю, что есть безопасный способ расчета смещений, предусмотренных стандартом, но мне любопытно, что вы скажете по этому поводу. Все в пользу моего объяснения: проголосуйте за этот вопрос :-)


person bert-jan    schedule 13.07.2011    source источник
comment
Если вы хотите сказать «По стандарту», ​​это совершенно безопасно. почему бы не предоставить стандартную ссылку для вашего утверждения?   -  person Mark B    schedule 13.07.2011
comment
Это может серьезно помешать множественному наследованию.   -  person Alexandre C.    schedule 13.07.2011
comment
Я открыт для мысли, что это небезопасно, но я хочу узнать ваше мнение   -  person bert-jan    schedule 13.07.2011
comment
@Alexandre C, никаких виртуальных базовых классов не предполагается! Ты прав   -  person bert-jan    schedule 13.07.2011
comment
По сути, это дубликат stackoverflow.com/q/2896689/293791   -  person Dennis Zickefoose    schedule 13.07.2011
comment
@ bert-jan: виртуальные базовые классы не нужны. Просто множественное наследование плохо работает с приведением типов C.   -  person Alexandre C.    schedule 13.07.2011
comment
@Dennis: Нет, это вообще не дубликат того вопроса. Указатель на член полностью отличается от указателя на объект ...   -  person Nemo    schedule 13.07.2011
comment
@Nemo: У него нет указателя на член, у него есть указатель на объект. Который он должен разыменовать, чтобы получить доступ к члену этого объекта.   -  person Dennis Zickefoose    schedule 13.07.2011
comment
Я думал только о простых структурах. Множественное наследование и виртуальные базовые классы могут нуждаться в объекте для вычисления адреса членов. И разница (смещение) может не быть постоянным во время компиляции. На самом деле это может очень время от времени во время выполнения.   -  person bert-jan    schedule 13.07.2011
comment
@Dennis: Я хочу сказать, что p = 0; q = &*p; полностью отличается от p = 0; q = &p->foo;. Я считаю, что первое является законным C99 (и это то, что задает повторяющийся вопрос), а второе - неопределенное поведение (и это то, что задает этот вопрос). Так что этот вопрос не только не повторяется, но и есть другой ответ. (По крайней мере, ничто, процитированное до сих пор в этом или другом вопросе, не служит для ответа на него.) Этот вопрос следует снова открыть, потому что он не дубликат.   -  person Nemo    schedule 13.07.2011
comment
перечисление {смещение1 = _OFFS_OF_MEMBER (структура a, b),}; // макрос используется там, где ожидается константа, и компилятор не жалуется. Должно быть, если параметр p_type является классом с виртуальным базовым классом или несколькими базовыми классами. Если компилятор не жалуется, это константа времени компиляции.   -  person bert-jan    schedule 13.07.2011
comment
@ bert-jan: Неважно, жалуется ли компилятор. Важно то, что сказано в спецификации. И теперь мы не узнаем ответа, потому что вопрос был неправильно закрыт как дубликат.   -  person Nemo    schedule 14.07.2011
comment
Настоящая проблема заключается в том, что с этими виртуальными базовыми классами и множественным наследованием невозможно вычислить смещение во время компиляции. Тем не менее, я считаю, что мой вопрос был полезен. Спасибо за ответ! А пока оставим это.   -  person bert-jan    schedule 14.07.2011


Ответы (5)


Вы здесь не разыменовываете что-либо недопустимое. Все, что делает этот макрос, - это сообщает компилятору, что структура типа p_type существует в памяти по адресу NULL. Затем он принимает адрес p_member, который является членом этой фиктивной структуры. Итак, никакого разыменования нигде.

Фактически, это именно то, что делает макрос offsetof, определенный в stddef.h.

РЕДАКТИРОВАТЬ:
Как говорится в некоторых комментариях, это может плохо работать с C ++ и наследованием, я использовал только offsetof со структурами POD в C.

person Praetorian    schedule 13.07.2011
comment
IIRC stddef.h использует адрес 0 в качестве базы, а не NULL - person David Heffernan; 13.07.2011
comment
@ Дэвид Хеффернан: Да, по крайней мере, по ссылке в Википедии, которую я опубликовал. - person Praetorian; 13.07.2011
comment
В стандартном C ++ 03 это недопустимо (5.2.5 / 3) - person Alexandre C.; 13.07.2011
comment
Этот макрос (во время выполнения) будет обращаться к памяти по соседству с адресом 0 из-за виртуального базового указателя, с которым необходимо обращаться. Но, как я уже сказал, это не мое намерение. - person bert-jan; 13.07.2011

Точно нет. Даже создать указатель, добавив смещение к NULL, означает вызвать Undefined Behavior. Кто-нибудь более мотивированный может откопать главу и стих из спецификации.

Между прочим, какой бы ни была ваша причина для вычисления этих смещений, она, вероятно, плохая.

person Nemo    schedule 13.07.2011
comment
ISO / IEC 14882: 2003, 5.2.5: 3. Если E1 имеет тип «указатель на класс X», то выражение E1->E2 преобразуется в эквивалентную форму (*(E1)).E2 (...). В этом отношении стандарты изменились. Например, макрос OP - это валидный C99 и, вероятно, действительный C ++ 0x. - person Alexandre C.; 13.07.2011
comment
@Alexandre: Вы можете процитировать соответствующий раздел C99? Я считаю, что единственные указатели, которые вам даже разрешено вычислять (не разыменование; просто вычислять), - это возврат из malloc (), указатель на массив (или один за концом) и сам NULL. - person Nemo; 13.07.2011
comment
§6.5.3.2 / 3, согласно этому ответу. Если операнд [унарного оператора &] является результатом унарного оператора *, ни этот оператор, ни оператор & не оцениваются, и результат будет таким, как если бы оба были опущены, за исключением того, что ограничения на операторы все еще применяются и результат не является lvalue. - person Alexandre C.; 13.07.2011
comment
@Alexandre: Но это не то же самое, что указатель на член ... Я на 99,9% уверен, что этот макрос недействителен даже в C99. (Также по этой причине этот вопрос НЕ повторяется.) - person Nemo; 13.07.2011

Это недопустимый C ++.

Из ISO / IEC 14882: 2003, 5.2.5:

3 / Если E1 имеет тип «указатель на класс X», то выражение E1-> E2 преобразуется в эквивалентную форму (* (E1)). E2 (...)

Тем не менее, об этом уже сообщалось о дефекте, и это действительный C99 (и, вероятно, действительный C ++ 0x тоже):

Из ISO / IEC 9899: 1999, 6.5.3:

2 / Если операнд [унарного оператора &] является результатом унарного оператора *, ни этот оператор, ни оператор & не оцениваются, и результат такой же, как если бы оба были опущены, за исключением того, что ограничения на операторы все еще применяются и результат не является lvalue.

person Alexandre C.    schedule 13.07.2011

Итак, &p->b is &(p->b) is (по определению) &((*p).b), что, похоже, предполагает разыменование p перед получением члена. Он может работать на большинстве компиляторов, даже если он нарушает стандарт. Как отмечено в комментарии, эта работа, вероятно, будет работать правильно в случаях, связанных с множественным наследованием.

Какую проблему вы пытаетесь решить, получив это смещение? Могли бы вы вместо этого использовать ссылки, указатели или указатели на член?

person Mark B    schedule 13.07.2011
comment
Я пытаюсь получить управляемую данными реализацию для хранения объекта в документе. Если объект сообщает различные смещения его подобъектов (подобъекты и сам объект имеют общий базовый класс), тогда я могу упростить хранение большого количества классов без необходимости добавлять виртуальные функции ко всем этим классам. - person bert-jan; 13.07.2011

#define _OFFS_OF_MEMBER(p_type, p_member) (size_t)&(((p_type *)NULL)->p_member)

struct a 
{
         int a, b;
};

size_t l = _OFFS_OF_MEMBER(struct a, b);

То же, что (после предварительной обработки)

struct a { int a, b; };
size_t l = (size_t)&(((struct a *)NULL)->b);

Я вижу, что вы приводите NULL к указателю на структуру a, а затем получаете адрес его члена b.

Насколько мне известно, поскольку вы получаете адрес b, но не получаете доступа или не изменяете (разыменовывая) значение b, компилятор не будет жаловаться, и вы не получите ошибку времени выполнения. Поскольку NULL (или 0) является начальным адресом a, это даст вам смещение. На самом деле это довольно изящный способ сделать это.

person Andrew Rasmussen    schedule 13.07.2011