После обсуждения с этот вопрос о нулевых указателях в C и C ++, я хотел бы разделить конечный вопрос здесь.
Если это может быть выведено из стандартов C и C ++ (ответы могут быть нацелены на оба стандарта), то разыменование переменной указателя, значение которой равно nullptr
(или же (void *)0
) значение — неопределенное поведение, подразумевает ли это, что эти языки требуют, чтобы специальное значение в адресном пространстве было мертвых, Это означает, что это непригодно для использования кроме роли nullptr
? Что если система имеет действительно полезную функцию или структуру данных по тому же адресу, который равен nullptr
? Должно ли это никогда не произойти, потому что это обязанность автора компилятора выяснить неконфликтующее значение нулевого указателя для каждой системы, в которую компилируется компилятор? Или программист, которому нужен доступ к такой функции или структуре данных, должен быть доволен, когда программирует в «неопределенном режиме поведения» для достижения своих целей?
Это похоже на размывание линий ролей компилятора и компьютерной системы. Я бы спросил, правильно ли это делать, но я думаю, здесь нет места для этого.
Означает ли это, что эти языки требуют, чтобы специальное значение в адресном пространстве было мертвым, а это означает, что его нельзя использовать, кроме роли представления
nullptr
?
Нет.
Компилятору необходимо специальное значение для представления нулевого указателя, и он должен позаботиться о том, чтобы он не помещал какой-либо объект или функцию по этому адресу, поскольку все указатели на объекты и функции должны сравниваться с нулевым указателем. Стандартная библиотека должна принимать аналогичные меры предосторожности при реализации malloc
и друзья.
Однако, если по этому адресу уже есть что-то, к чему не имеет доступа ни одна строго согласованная программа, то реализация является разрешено поддерживать разыменование нулевого указателя для доступа к нему. Разыменование нулевого указателя в стандарте C не определено, поэтому реализация может заставить его делать все что угодно, включая очевидное.
Стандарты C и C ++ понимают концепцию как будто правило, которое в основном означает, что если для правильного ввода, реализация неотличима от той, которая соответствует стандарту, то она делает соответствовать стандарту. Стандарт C использует тривиальный пример:
5.1.2.3 Выполнение программы
10 Пример 2. При выполнении фрагмента
char c1, c2; /* ... */ c1 = c1 + c2;
«целочисленные продвижения» требуют, чтобы абстрактная машина повышала значение каждой переменной до
int
размер, а затем добавить дваint
и усечь сумму. Предусмотрено добавление двухchar
с можно обойтись без
Переполнение, или с автоматическим переносом переполнения для получения правильного результата, фактическое выполнение должно давать только тот же результат, возможно, пропуская рекламные акции.
Сейчас если c1
а также c2
значения поступают из регистров, и можно принудительно устанавливать значения вне char
Диапазон этих регистров (например, с помощью встроенной сборки), тогда факт, что реализация оптимизирует целочисленные продвижения, может быть заметным. Однако, поскольку единственный способ наблюдать это — через неопределенное поведение или расширения реализации, это никак не повлияет на любой стандартный код, и реализации разрешено это делать.
Это та же логика, которая применяется для получения полезных результатов при разыменовании нулевых указателей: из кода есть только два способа увидеть, что по этому конкретному адресу есть что-то значимое: получить нулевой указатель из оценки, которая гарантированно выдаст указатель на объект или просто попробуйте. Первый — это то, о чем я говорил, компилятор и стандартная библиотека должны заботиться. Последнее не может повлиять на действующую стандартную программу.
Хорошо известным примером является таблица векторов прерываний в реализациях DOS, которая находится по адресу ноль. Доступ к нему обычно осуществляется путем разыменования нулевого указателя. Стандарты C и C ++ не охватывают, не должны и не могут охватывать доступ к таблице векторов прерываний. Они не определяют такое поведение, но и не ограничивают доступ к нему. Реализации должны быть и могут предоставлять расширения для доступа к нему.
Это зависит от того, что подразумевается под фразой «адресное пространство». Стандарт C использует фразу неформально, но не определяет, что это значит.
Для каждого типа указателя должен быть значение (нулевой указатель), который сравнивается с указателем на любой объект или функцию. Это означает, например, что если тип указателя имеет ширину 32 бита, то может быть не более 232-1 допустимые ненулевые значения этого типа. Их может быть меньше, если некоторые адреса имеют более одного представления или если не все представления соответствуют действительным адресам.
Так что если вы определите «адресное пространство» для покрытия 2N отдельные адреса, где N — ширина в битах указателя, тогда да, одно из этих значений должно быть зарезервировано как значение нулевого указателя.
С другой стороны, если «адресное пространство» уже, чем это (например, типичные 64-битные системы фактически не могут получить доступ к64 различные области памяти), то значение, зарезервированное как нулевой указатель, может легко оказаться за пределами «адресного пространства».
Некоторые вещи на заметку:
В большинстве современных реализаций все типы указателей являются один и тот же размер, и все они представляют нулевой указатель как все-бит-ноль, но есть веские причины, например, сделать указатели функций шире, чем указатели объектов, или сделать void*
шире чем int*
или используйте представление, отличное от all-bits-zero, для нулевого указателя.
Этот ответ основан на стандарте C. Большая часть этого также относится к C ++. (Одно отличие состоит в том, что в C ++ есть типы указателей на члены, которые обычно шире, чем обычные указатели.)
Означает ли это, что эти языки требуют, чтобы специальное значение в адресном пространстве было мертвым, а это означает, что оно непригодно для использования, кроме роли представления nullptr?
Да.
C имеет требования для нулевого указателя, которые отличают его от указателей объекта:
(C11, 6.3.2.3p3) «[…] Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно сравнивает неравный указатель с любым объектом или функцией.»
Что если система имеет действительно полезную функцию или структуру данных по тому же адресу, который равен nullptr? Должно ли это никогда не произойти, потому что автором компилятора является обязанность выяснить неконфликтующее значение нулевого указателя для каждой системы, в которую компилируется компилятор?
Новый стандарт C Дерека М. Джонса предоставляет следующий комментарий к реализации:
Все биты ноль — это удобное представление времени выполнения константы нулевого указателя для многих реализаций, потому что это неизменно самый низкий адрес в памяти. (Транспутер ИНМОС [632] подписал
адресное пространство, которое помещается в ноль посередине.) Хотя может быть информация о загрузке программы в
В этом месте маловероятно, что какие-либо объекты или функции будут размещены здесь. Многие операционные системы уходят
это место хранения не используется, поскольку опыт показывает, что ошибки программы иногда приводят к тому, что значения
записывается в местоположение, указанное константой нулевого указателя (более ориентированные на разработчика среды пытаются
вызвать исключение при доступе к этому местоположению).Другой метод реализации, когда среда хоста не включает нулевой адрес как часть
обрабатывает адресное пространство, заключается в создании объекта (иногда называемого _ _null) как части стандартной библиотеки. Все
ссылки на константу нулевого указателя ссылаются на этот объект, адрес которого будет сравниваться с любым другим
объект или функция.
Да, это именно то, что это значит.
[C++11: 4.10/1]:
[..] Константа нулевого указателя может быть преобразована в тип указателя; результатом является значение нулевого указателя этого типа и отличается от любого другого значения указателя объекта или типа указателя на функцию. [..]
Значение нулевого указателя не должно быть 0x00000000
, но он должен быть уникальным; нет другого способа заставить это правило работать.
Это, конечно, не единственное правило абстрактной машины, которое неявно накладывает строгие ограничения на практические реализации.
Что если ОС поместит действительно полезную функцию или структуру данных по тому же адресу, который равен nullptr?
ОС не будет этого делать, но это может быть использовано.