Доступ к полю NULL-указателя на структуру работает, если используется адрес-адрес [дубликат]

54
задан M.M 13 November 2014 в 11:27
поделиться

6 ответов

Начнем с оператора косвенности *:

6.5.3.2 p4: Оператор унарного * обозначает косвенность. Если операнд указывает на функцию, результат будет обозначать функцию; если он указывает на объект, результатом будет lvalue, обозначающий объект. Если операнд имеет тип «указатель на тип», результат имеет тип «тип». Если для указателя присвоено недопустимое значение, поведение унарного * оператора не определено. 102)

* E, где E - нулевой указатель, является неопределенным поведением.

Существует сноска, которая гласит:

< blockquote>

102) Таким образом, &*E эквивалентно E (даже если E - нулевой указатель) и & amp; (E1 [E2]) - ((E1) + (E2 )). Всегда верно, что если E является обозначением функции или значением l, которое является допустимым операндом унарного & amp; оператор, * & amp; E - обозначение функции или lvalue, равное E. Если * P - это lvalue, а T - имя типа указателя объекта, * (T) P - это значение l, которое имеет тип, совместимый с тем, что где T указывает.

Это означает, что & amp; * E, где E является NULL, определяется, но вопрос в том, верно ли то же самое для & amp; (* E) .m, где E - нулевой указатель, а его тип - это структура, которая имеет член m?

. Стандарт C не определяет это поведение.

Если бы оно было определено, возникли новые проблемы , один из которых приведен ниже. C Standard корректен, чтобы сохранить его неопределенным и предоставляет макрос offset, который обрабатывает проблему внутренне.

6.3.2.3 Указатели

  1. Целочисленное постоянное выражение с значение 0 или такое выражение, отлитое от типа void *, называется константой нулевого указателя. 66) Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно сравнится неравномерно с указателем на любой объект или функцию.

Это означает, что целочисленное постоянное выражение со значением 0 преобразуется в константу нулевого указателя.

Но значение константы нулевого указателя не определено как 0. Значение определено реализацией.

7.19 Общие определения

  1. Макросы имеют значение NULL, которое расширяется до константы нулевого указателя, определяемой реализацией

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

. Другая проблема заключается в том, как вы оцениваете & amp; (* E) .m? Применяются ли скобки и сначала оцениваются *. Решение этой проблемы решает эту проблему.

10
ответ дан 2501 25 August 2018 в 10:16
поделиться

Нет. Давайте разделим это:

&(((struct name *)NULL)->b);

совпадает с:

struct name * ptr = NULL;
&(ptr->b);

Первая строка, очевидно, действительна и определена корректно.

Во втором line, мы вычисляем адрес поля относительно адреса 0x0, который также является совершенно законным. Например, у Amiga был указатель на ядро ​​в адресе 0x4. Таким образом, вы можете использовать такой метод, чтобы вызвать функции ядра.

На самом деле тот же подход используется для макроса C offsetof ( wikipedia ):

#define offsetof(st, m) ((size_t)(&((st *)0)->m))

Таким образом, путаница здесь вращается вокруг того, что указатели NULL страшны. Но из компилятора и стандартной точки зрения выражение является законным в C (C ++ - это другой зверь, поскольку вы можете перегрузить оператор &).

0
ответ дан Aaron Digulla 25 August 2018 в 10:16
поделиться

Сначала давайте установим, что нам нужен указатель на объект:

6.5.2.3 Элементы структуры и объединения

4 Постфиксное выражение, за которым следует -> оператор и идентификатор обозначает член структуры или объект объединения. Это значение имеет именованный элемент объекта, к которому относится первое выражение, и является значением lvalue.96). Если первое выражение является указателем на квалифицированный тип, результат имеет соответствующую квалификацию версии типа назначенный член.

К сожалению, никакой нулевой указатель никогда не указывает на объект.

6.3.2.3 Указатели

3 Целочисленное константное выражение со значением 0 или такое выражение, отличное от типа void *, называется константой нулевого указателя .66) Если константа нулевого указателя преобразуется в указатель type, полученный указатель, называемый нулевым указателем , гарантированно сравнится неравномерно с указателем на любой объект или функцию.

Результат: неопределенное поведение.

Как побочная заметка, некоторые другие вещи, чтобы пережевать:

6.3.2.3 Указатели

4 Преобразование нулевого указателя в другой тип указателя дает нулевой указатель этого типа. Любые два нулевых указателя сравнивают одинаковые. 5 Целое число может быть преобразовано в любой тип указателя. За исключением, как указано ранее, результат определяется реализацией, может быть не правильно выровнен, может не указывать на объект ссылочного типа и может быть ловушечным представлением.67) 6 Любой тип указателя может быть преобразован в целочисленный тип. За исключением случаев, указанных ранее, результат определяется реализацией. Если результат не может быть представлен в целочисленном типе, поведение не определено. Результат не должен находиться в диапазоне значений любого целочисленного типа.

67) Функции отображения для преобразования указателя на целое или целое число в указатель должны соответствовать структуре адресации среда выполнения.

Таким образом, даже если UB должно быть доброкачественным на этот раз , это может привести к некоторому совершенно неожиданному числу.

4
ответ дан Deduplicator 25 August 2018 в 10:16
поделиться

Да, это использование -> имеет неопределенное поведение в прямом смысле английского термина undefined.

Поведение определяется только в том случае, если первое выражение указывает на объект и не определено (= undefined ) в противном случае. В общем, вы не должны искать больше в терминах undefined, это означает только то: стандарт не дает смысла для вашего кода. (Иногда он явно указывает на такие ситуации, которые он не определяет, но это не меняет общий смысл этого термина.)

Это вялость, которая вводится, чтобы помочь разработчикам компилятора справиться с вещи. Они могут определять поведение даже для кода, который вы представляете. В частности, для реализации компилятора отлично использовать такой код или аналогичный для макроса offsetof. Приведение этого кода в нарушение ограничений блокирует этот путь для реализации компилятора.

16
ответ дан Jens Gustedt 25 August 2018 в 10:16
поделиться

С точки зрения адвоката выражение &(((struct name *)NULL)->b); должно привести к UB, поскольку вы не смогли найти путь, в котором не было бы UB. ИМХО, первопричиной является то, что на мгновение вы применяете оператор -> к выражению, которое не указывает на объект.

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

Как написано, мы не могли обвинить компилятор, который отметил бы, что во внутренней части вы используете оператор -> для выражения, чем не можете указать на объект (поскольку он является нулевым) и выдавать предупреждение или ошибку.

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

21
ответ дан Serge Ballesta 25 August 2018 в 10:16
поделиться

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

  1. Код загружает нулевой указатель в блок адресации
  2. Код просит блок адресации добавить смещение поля b.
  3. Блок адресации запускает ловушку при попытке добавить целое число в нулевой указатель (который должен для надежности это ловушка времени выполнения, хотя многие системы ее не улавливают).
  4. Система запускает по существу случайный код после отправки через вектор-ловушку, который никогда не был установлен, потому что код для его установки потеряли память, поскольку адресация ловушек не должна происходить.

Сама суть того, что в то время означало неопределенное поведение.

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

0
ответ дан supercat 25 August 2018 в 10:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: