Ответ 1
Несмотря на то, что он ничего не делает, char* foo = 0; *foo;
является может быть undefined.
Вызов нулевого указателя может быть undefined. И да, ptr->foo
эквивалентно (*ptr).foo
, а *ptr
вызывает нулевой указатель.
В рабочих группах сейчас открытая проблема, если поведение *(char*)0
undefined, если вы его не читаете или не пишете. Части стандарта подразумевают это, другие части подразумевают, что это не так. Текущие заметки, похоже, склоняются к тому, чтобы определить его.
Теперь, это теоретически. Как на практике?
В большинстве компиляторов это работает, потому что проверки не выполняются во время разыменования: память вокруг нулевого указателя указывает на защиту от доступа, и вышеприведенное выражение просто принимает адрес чего-то вокруг нуля, оно не читает и не записывает там.
Вот почему ссылка cpp offsetof
перечисляет в значительной степени этот трюк как возможную реализацию. Тот факт, что некоторые (многие? Большинство? Каждый, кого я проверил?), Компиляторы реализуют offsetof
аналогичным или эквивалентным образом, не означает, что поведение хорошо определено в стандарте С++.
Однако, учитывая неоднозначность, компиляторы могут свободно добавлять проверки в каждую инструкцию, которая разделяет указатель, и выполнять произвольный код (например, при сбое быстрого сообщения об ошибках), если null действительно разыменован. Такая аппаратура может быть даже полезной для поиска ошибок, где они происходят, а не там, где происходит симптом. И в системах, где имеется доступная для записи память около 0
, такая аппаратура может быть ключевой (у pre OSX MacOS было некоторое количество записываемой памяти, которая контролировала системные функции около 0
).
Такие компиляторы могли бы написать offsetof
таким образом и ввести pragma
или тому подобное, чтобы заблокировать инструментарий в сгенерированном коде. Или они могут переключиться на внутреннюю.
Идя дальше, С++ оставляет много широты в том, как организованы данные нестандартной компоновки. Теоретически классы могут быть реализованы как довольно сложные структуры данных, а не почти стандартные структуры макетов, которые мы ожидали, и код будет по-прежнему действительным С++. Доступ к переменным-членам к типам нестандартного макета и их адреса может быть проблематичным: я не знаю, есть ли какая-либо гарантия того, что смещение переменной-члена в нестандартном макете не меняется между экземплярами!
Наконец, некоторые компиляторы имеют агрессивные параметры оптимизации, которые находят код, который выполняет поведение undefined (по крайней мере, в определенных ветвях или условиях) и использует это, чтобы отметить эту ветвь как недостижимую. Если будет принято решение о том, что нулевое разыменование является undefined, это может быть проблемой. Классическим примером является gcc-атакующий выпрямитель целых чисел с переполнением. Если стандарт диктует что-то, это поведение undefined, компилятор может считать эту ветвь недостижимой. Если нулевое разыменование не находится за веткой в функции, компилятор может объявить весь код, который вызывает эту функцию, недоступен и рекурсивно.
И было бы свободно делать это не в текущей, а в следующей версии вашего компилятора.
Написание кода, который является стандартным, - это не просто код, который сегодня компилируется. Хотя степень разыменования и не использующая нулевой указатель в настоящее время неоднозначна, полагаясь на то, что только двусмысленно определено, является рискованным.