Выполняет ли арифметика с нулевым указателем неопределенное поведение?

Мне кажется, что следующая программа вычисляет неверный указатель, так как NULL ничего хорошего, кроме присваивания и сравнения на равенство:

#include <stdlib.h>
#include <stdio.h>

int main() {

char *c = NULL;
c--;

printf("c: %p\n", c);

return 0;
}

Однако, похоже, что ни одно из предупреждений или инструментария в GCC или Clang, нацеленных на неопределенное поведение, не говорит о том, что это на самом деле UB. Действительно ли эта арифметика действительна, и я слишком педантичен, или это недостаток в их механизмах проверки, о которых я должен сообщить?

Проверено:

$ clang-3.3 -Weverything -g -O0 -fsanitize=undefined -fsanitize=null -fsanitize=address offsetnull.c -o offsetnull
$ ./offsetnull
c: 0xffffffffffffffff

$ gcc-4.8 -g -O0 -fsanitize=address offsetnull.c -o offsetnull
$ ./offsetnull
c: 0xffffffffffffffff

Кажется, довольно хорошо задокументировано, что AddressSanitizer, используемый Clang и GCC, больше ориентирован на разыменование плохих указателей, так что это достаточно справедливо. Но другие проверки тоже не улавливают: — /

редактировать: часть причины, по которой я задал этот вопрос, заключается в том, что -fsanitize флаги позволяют динамический проверки четкости в сгенерированном коде. Это то, что они должны были поймать?

17

Решение

Арифметика указателя на указатель, не указывающий на массив, является неопределенным поведением.
Кроме того, разыменование нулевого указателя является неопределенным поведением.

char *c = NULL;
c--;

Неопределенное определенное поведение, потому что c не указывает на массив.

C ++ 11 Standard 5.7.5:

Когда выражение, имеющее целочисленный тип, добавляется или вычитается из указателя, результат имеет тип операнда указателя. Если операнд-указатель указывает на элемент объекта массива, и массив достаточно велик, результат указывает на смещение элемента от исходного элемента, так что разность индексов результирующего и исходного элементов массива равна интегральному выражению. Другими словами, если выражение P указывает на i-й элемент объекта массива, выражения (P) + N (эквивалентно, N + (P)) и (P) -N (где N имеет значение n) указывают соответственно i + n-м и i-n-м элементам массива, если они существуют. Кроме того, если выражение P указывает на последний элемент объекта массива, выражение (P) +1 указывает один за последним элементом объекта массива, и если выражение Q указывает на один последний элемент последнего элемента массива, выражение (Q) -1 указывает на последний элемент объекта массива. Если и операнд указателя, и результат указывают на элементы одного и того же объекта массива или одного последнего
последний элемент объекта массива, оценка не должна производить переполнение; в противном случае поведение не определено.

19

Другие решения

Да, это неопределенное поведение, и это то, что -fsanitize=undefined должен был поймать; это уже в моем списке TODO, чтобы добавить проверку для этого.

FWIW, правила C и C ++ здесь немного отличаются: добавление 0 для нулевого указателя и вычитания одного нулевого указателя из другого имеют неопределенное поведение в C, но не в C ++. Вся другая арифметика с нулевыми указателями имеет неопределенное поведение в обоих языках.

16

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

Стандартом никогда не определяется ситуация, в которой добавление чего-либо к нулевому указателю может привести к допустимому значению указателя; кроме того, ситуации, в которых реализации могут определять любое полезное поведение для таких действий, встречаются редко и, как правило, их лучше обрабатывать с помощью встроенных функций компилятора (*). Однако во многих реализациях, если арифметика нулевого указателя не перехвачена, добавление смещения к нулевому указателю может привести к указателю, который, хотя и недопустим, больше не является узнаваемый как нулевой указатель. Попытка разыменования такого указателя не будет захвачена, но может вызвать произвольные эффекты.

Перечисление вычислений указателя в форме (null + offset) и (null-offset) устранит эту опасность. Обратите внимание, что защита не обязательно требует прерывания (pointer-null), (null-pointer) или (null-null), в то время как значения, возвращаемые первыми двумя выражениями, вряд ли будут иметь какую-либо полезность [если реализация должна была бы указывать что null-null даст ноль, код, который нацелен на эту конкретную реализацию, иногда может быть более эффективным, чем код, который был на особый случай null] они не будут генерировать недействительные указатели. Кроме того, наличие (null + 0) и (null-0) либо дает нулевые указатели, чем перехватывает, не ставит под угрозу безопасность и может избежать необходимости иметь пользовательские коды нулевых указателей специального случая, но преимущества будут менее убедительными, так как компилятор пришлось бы добавить дополнительный код, чтобы это произошло.

(*) Такая встроенная функция в компиляторах 8086, например, может принимать 16-разрядные целые числа без знака «seg» и «ofs» и читать слово по адресу seg: ofs без нулевой ловушки, даже если адрес оказался нулевым , Адрес (0x0000: 0x0000) на 8086 является вектором прерывания, к которому некоторым программам может понадобиться доступ, и в то время как адрес (0xFFFF: 0x0010) обращается к тому же физическому местоположению, что и (0x0000: 0x0000) на более старых процессорах с только 20 адресными строками, это доступ к физическому местоположению 0x100000 на процессорах с 24 или более адресными строками). В некоторых случаях альтернативой может быть специальное обозначение указателей, которые ожидаемый указывать на вещи, не распознаваемые стандартом C (такие вещи, как векторы прерываний, могут быть определены), и воздерживаться от их обнуления, или же указать, что volatile указатели будут обрабатываться таким образом. Я видел первое поведение по крайней мере в одном компиляторе, но не думаю, что видел второе.

6
По вопросам рекламы [email protected]