Адрес нулевого указателя? - PullRequest
       18

Адрес нулевого указателя?

8 голосов
/ 22 сентября 2011

Я наткнулся на макрос ниже

#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))

Я как бы не в состоянии переварить это, потому что в c ++, когда я пытаюсь определить нулевой указатель, я ожидаю неожиданного поведения ... но почему у него может быть адрес? что означает адрес null?

Ответы [ 8 ]

11 голосов
/ 22 сентября 2011

Для целей макроса: он предполагает наличие объекта типа TYPE по адресу 0 и возвращает адрес члена, который фактически является смещением члена в структуре.

Этот ответ объясняет, почему это неопределенное поведение.Я думаю, что это самая важная цитата:

Если E1 имеет тип «указатель на класс X», то выражение E1->E2 преобразуется в эквивалентную форму (*(E1)).E2;*(E1) приведет к неопределенному поведению со строгой интерпретацией, а .E2 преобразует его в r-значение, делая его неопределенным поведением для слабой интерпретации.Хотя другие считают, что это действительно так.Важно отметить, что это даст правильный результат на многих компиляторах.

8 голосов
/ 22 сентября 2011
#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))

очень похоже на довольно распространенное определение стандартного макроса offsetof(), определенного в <stddef.h> (в C) или <cstddef> (в C ++).

0 is константа нулевого указателя .Приведение его к TYPE * дает нулевой указатель типа TYPE *.Обратите внимание, что язык не гарантирует (и даже не подразумевает), что нулевой указатель имеет значение 0, хотя это очень часто встречается.

Так что (TYPE *)0 - это условно адрес объекта типа TYPEнаходится по любому адресу, на который указывает нулевой указатель, а ((TYPE *)0)->ELEMENT)) является членом ELEMENT этого объекта.

Оператор & получает адрес этого члена ELEMENT, и преобразование преобразует этоадрес для типа size_t.

Теперь , если пустой указатель указывает на адрес 0, тогда (несуществующий) объект типа TYPE начинается с адреса 0, иадрес члена ELEMENT этого объекта находится по адресу, который смещен на некоторое количество байтов от адреса 0. Предполагается, что преобразование, определенное реализацией из TYPE * в size_t, ведет себя прямым образом (что-то еще, что негарантируется языком), результатом всего выражения будет смещение элемента ELEMENT в объекте типа TYPE.

All tон зависит от нескольких неопределенных или неопределенных форм поведения.В большинстве современных систем нулевой указатель реализован в виде указателя на адрес 0, адреса (значения указателя) представляются так, как если бы они были целыми числами, определяющими индекс конкретного байта в монолитном адресном пространстве, и преобразуют указатель в целое число.одного и того же размера просто переосмысливает биты.В системе с такими характеристиками макрос OFFSETOF, скорее всего, будет работать, и реализация может использовать аналогичное определение для стандартного макроса offsetof.(Код, являющийся частью реализации, может использовать преимущества поведения, определенного или неопределенного поведения; не обязательно быть переносимым.)

В системах, которые не имеют этих характеристик, этот макрос OFFSETOF может не работать- и реализация должна использовать какой-то другой метод для реализации offsetof.Вот почему offsetof является частью стандартной библиотеки;он не может быть реализован переносимо, но он всегда может быть реализован некоторым способом для любой системы.И некоторые реализации используют магию компилятора, например, gcc __builtin_offsetof.

На практике не имеет особого смысла определять свой собственный макрос OFFSETOF, как это, так как любая соответствующая реализация на C или C ++ обеспечитрабочий макрос offsetof в стандартной библиотеке.

6 голосов
/ 22 сентября 2011

Это не разыменование указателя, а возвращение смещения элемента в структуре.

, например, для

typedef struct { char a; char b;} someStruct;

Вызов OFFSETOF(someStruct, b) вернет 1 (при условии, что он упаковани т. д.).

Это то же самое, что и это:

someStruct str;
offset = (size_t)&(str.b) - (size_t)&str;

, за исключением того, что с OFFSETOF вам не нужно создавать фиктивную переменную.

Это необходимо, когда вам нужно найти смещение члена класса / структуры / объединения по какой-либо причине.

** Редактировать **

Всем поспешным даунвотерам, которые думают, что «стандарт»не позволяет этого "- пожалуйста, прочитайте стандарт еще раз.Поведение в этом случае очень хорошо определено.

** Другое редактирование **

Я полагаю, что ни один из downvoters не заметил, что первый параметр - type .Я уверен, что если вы думаете, что понизить голос занимает чуть больше полсекунды, вы поймете свою ошибку.Если нет - ну, это будет не первое, что группа невежественных даунвотеров подавила правильный ответ.

4 голосов
/ 22 сентября 2011

Разыменование нулевого указателя (как это делает макрос) - неопределенное поведение. Вы не можете писать и использовать такой макрос, если только реализация дает вам некоторую специальную дополнительную гарантию.

Стандартная библиотека C определяет макрос offsetof; много реализаций используйте что-то похожее на это. Реализация может сделать это, потому что он знает, что на самом деле генерирует компилятор в этом случае, и это вызовет проблемы или нет. Реализация стандарта библиотека может использовать много вещей, которые вы не можете.

4 голосов
/ 22 сентября 2011

Цель OFFSETOF - вернуть расстояние между адресом элемента и адресом агрегата, которому он принадлежит.

Если компилятор не меняет компоновку объекта в зависимости от его размещения,это «расстояние» является постоянным, и, следовательно, адрес, с которого вы начинаете, не имеет значения.0, в таком случае это просто адрес, как и любой другой.

В соответствии со стандартом C ++ доступ к недопустимому адресу является «неопределенным поведением», но:

  • Еслиэто часть библиотеки поддержки компилятора (это фактический код «OFFSETOF» в CRT, поставляемом с VS2003!), который может быть не таким «неопределенным» (для известного компилятора и платформы такое поведение известно библиотеке поддержкиразработчик: конечно, это должно рассматриваться как «код, специфичный для платформы», но разные платформы, вероятно, будут иметь разные версии библиотеки)

  • В любом случае, вы не «действуете» на элемент(так что «доступ» не делается), просто делаю простую арифметику с указателями.Примите во внимание общую демонстрацию типа " Если в местоположении 0 есть объект, его предполагаемый элемент ELEMENT начнет все местоположение 6. Следовательно, 6 - это смещение ".Тот факт, что real такой объект отсутствует, не имеет значения.

  • Кстати, этот макрос завершается ошибкой (с ошибкой сегментации!), Если ELEMENT наследуетсяТИП С помощью виртуальной базы, поскольку для определения местоположения базы виртуальной базы вам необходим доступ к некоторым данным времени выполнения - обычно это часть таблицы v-таблицы, местоположение которой не может быть обнаружено, являющейся адресом объектане "настоящий" адрес.Вот , почему стандарт осторожно говорит, что "разыменование недопустимого указателя является неопределенным поведением".


ДЛЯ УКАЧИВАТЕЛЕЙ:

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

3 голосов
/ 22 сентября 2011

Это один адский макрос, накапливающий неопределенное поведение ...

Что он пытается сделать: получить смещение struct члена.

Как он пытается это сделать:

  • Использовать нулевой указатель (значение 0 в коде)
  • Взять элемент (пусть компилятор вычислит его адрес, с 0)
  • Взять адрес элемента (используя &)
  • Ввести адрес в size_t

Есть две проблемы:

  • Разыменование нулевого указателя является неопределенным поведением, поэтому технически все может произойти
  • Приведение указателя в size_t не является чем-то, что должно быть сделано (проблема в том, что указатель не гарантированно помещается)

Как это можно сделать:

  • Использовать реальный объект
  • Вычислить разницу адресов

В коде:

#define OFFSETOF(Object, Member) \
  ((diffptr_t)((char*)(&Object.Member) - (char*)(&Object))

Однако для этого требуется объект, поэтому он может не подходить для ваших целей.

Как это сделать :

#include <cstddef>
#define OFFSETOF(Struct, Member) offsetof(Struct, Member)

Но было бы мало смысла ... верно?

Для любопытных определение может быть примерно таким: __builtin_offsetof(st, m) (из Wikipedia ). Некоторые компиляторы реализуют его с нулевыми разыменованиями, но они являются компиляторами, и поэтому знают , что они обрабатывают этот случай безопасно; это не переносимо ... и не должно быть, так как переключая компилятор, вы также переключаете реализацию библиотеки C.

3 голосов
/ 22 сентября 2011

A. Действие допустимо, исключение не выдается, поскольку вы не пытаетесь получить доступ к памяти, на которую указывает указатель.
B. нулевой указатель - это в основном нормальный указатель, говорящий, что объект находится по адресу 0 (адрес 0 по определению является недействительным адресом для реальных объектов), но указатель сам по себе действителен

Итак, этот макрос имеет в виду: если объект типа TYPE начинается с адреса 0, где будет находиться его ELEMENT в памяти? другими словами, каково смещение от ELEMENT до начала объекта TYPE.

2 голосов
/ 22 сентября 2011

littleadv имел правильное намерение построить.Объяснение немного: Вы приводите указатель структуры, указывающий на адрес 0x0 и разыменование его элементов.Адрес, на который вы указываете, теперь равен 0x0 + независимо от смещения элемента.Теперь вы приводите это значение к size_t и получаете смещение элемента.

Хотя я не уверен, насколько переносима эта конструкция.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...