Является ли непостоянное нулевое целое число, приведенное к `void *`, по-прежнему нулевым указателем?

Выражение (void *)0 называется нулевым указателем. Но как насчет следующего:

int i = 0;
void *s = (void *)i;

Является ли s также нулевым указателем? Стандарт языка C гласит:

6.3.2.3 Указатели

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

4. Преобразование нулевого указателя в другой тип указателя дает нулевой указатель этого типа. Любые два нулевых указателя должны сравниваться равными.

5 Целое число может быть преобразовано в указатель любого типа. Если не указано иное, результат определяется реализацией, может быть неправильно выровнен, может не указывать на сущность ссылочного типа и может создавать неопределенное представление при сохранении в объекте.71)

Согласно этому s не будет нулевым указателем?

Часто задаваемые вопросы по C 5.18 спрашивают: «Является ли целочисленное значение 0 во время выполнения, приведенное к указателю, гарантированно нулевым указателем?» и отвечает «Нет». Так что, казалось бы, я ошибаюсь. Мне было бы любопытно, если бы кто-нибудь нашел текущую машину, на которой она не работает, но, по-видимому, теоретически результат приведения целочисленной переменной, содержащей ноль, не обязательно должен быть нулевым указателем. (Небольшое предостережение: FAQ по C был написан в предыдущем тысячелетии, но правила, вероятно, не изменились.)

Jonathan Leffler 06.06.2024 07:23

@JonathanLeffler Одним из примеров, который имеет указатели объектов с целочисленным значением, равным нулю, является обычный компилятор Keil C(x)51 для семейства MCU 8051, широко используемой гарвардской архитектуры. Нулевой указатель функции указывает на код сброса. Нулевой общий указатель указывает на ячейку ДАННЫХ по адресу 0 внутренней оперативной памяти. Указатель, специфичный для нулевой памяти, указывает на соответствующую ячейку памяти по адресу 0 этой памяти.

the busybee 06.06.2024 08:49

@JonathanLeffler Однако Keil C(x)51 не является совместимым компилятором. :-D Из-за его реализации нет никаких шансов получить совместимый NULL, за исключением общих указателей в принципе. Но, насколько мне известно, Кейл не воспользовался этой возможностью.

the busybee 06.06.2024 08:50

@JonathanLeffler Многие микроконтроллеры имеют регистры, отображенные в памяти по нулевому адресу (например, все со вкусом Motorola) — у вас будет что-то вроде #define REGNAME (*(volatile uint8_t*)0), и оно используется для доступа к регистру, а не как нулевой указатель.

Lundin 20.06.2024 13:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
4
163
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

В стандартном обосновании это ясно указано. Из Обоснования C99, редакция 5.10, 6.3.2.3:

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

Выражение (void *)0 называется нулевым указателем.

Это не. Это называется константой нулевого указателя, особым элементом языка, который может иметь форму (void*)0 или 0 и используется с целью превращения указателя объекта/функции в нулевой указатель. Посмотрите В чем разница между нулевыми указателями и NULL?

Является ли s также нулевым указателем?

Это не так, поскольку нулевой указатель может быть сформирован только из констант нулевого указателя. Соответствующей частью стандарта C действительно является цитируемый пункт 6.3.2.3. Обратите внимание на целочисленное константное выражение. (void *)i не является целочисленным константным выражением.

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

Например, теоретически int* ptr = 0; может привести к тому, что ptr будет содержать значение 0xFFFFFFFF или что-то в этом роде - компилятор должен затем вмешаться и назначить это необработанное представление «между строк» ​​всякий раз, когда он обнаружит присвоение константы нулевого указателя.

Однако на практике компиляторы для систем, где 0 является допустимым адресом, обычно этого не делают, а используют представление нулевого указателя как 0. У меня были подобные ошибки в системах микроконтроллеров, где случайное присвоение нулевого указателя вызвал активацию GPIO. Довольно опасно, очевидно.

Предположительно, существуют некоторые ISA, которые на самом деле разработаны для C, используют адрес 0 для представления нулевого указателя, но рассматривают запись по этому адресу как программное исключение/ловушку. Или в случае отображения виртуальной памяти MMU может сделать адрес 0 недействительным.

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

user2357112 20.06.2024 13:05

Также см. 6.5.9p6, который требует, чтобы нулевой указатель не мог сравниваться равным с каким-либо указателем, кроме другого нулевого указателя. Если реализация говорит, что (int*)0 и (int*)x сравниваются равными, где x равно 0, то (int*)x должен быть нулевым указателем на эту реализацию.

user2357112 20.06.2024 13:27

@ user2357112 В стандарте C нет ничего существенного, позволяющего создавать нулевой указатель таким образом. Это не зависит от реализации, это нестандартные расширения языка. Что касается (int*)0, то это не гарантирует получение нулевого указателя, поскольку стандарт явно говорит, что 0 необходимо привести к void*, чтобы получить константу нулевого указателя. Также, как упоминалось в этом ответе, многие компиляторы рассматривают нулевые указатели не так, как предполагалось, а просто как адрес 0. Это очень запутанная часть языка в целом, так было всегда - обоснования для нулевых указателей повсюду.

Lundin 20.06.2024 13:55
0 уже является константой нулевого указателя. Вы неправильно прочитали этот раздел. Существует 3 варианта константы нулевого указателя: «Целочисленное константное выражение со значением 0, например, выражение, приведенное к типу void *, или предопределенная константа nullptr». 0 подпадает под первый вариант. Поскольку 0 является константой нулевого указателя, (int*)0 должен быть нулевым указателем.
user2357112 20.06.2024 14:04

@user2357112 user2357112 Ах да, достаточно справедливо. В любом случае, я не могу придумать ни одного компилятора или системы, которая бы обрабатывала нулевые указатели иначе, чем указатели на объекты с нулевым адресом. Вероятно, есть некоторые архаичные чудаки, которые составляют исключение.

Lundin 20.06.2024 14:35

Другие вопросы по теме

Требуется ли для создания экземпляров шаблонов классов использовать только указатель или ссылку на них?
Строгое псевдонимирование первого члена структуры через непрозрачный указатель в C
Определение макросов с тем же именем, что и функции стандартной библиотеки
Является ли передача ссылки на примитив из контекста C++ в контекст C неопределенным поведением?
Использование оператора безопасного вызова Kotlin
Является ли неопределенным поведение передача указателя на несконструированный объектstreambuf в конструктор ostream?
Является ли рекурсивный вызов main из его собственных параметров (злоупотребление sizeof с помощью VLA) стандартом C99?
Перегрузка и нестабильность
До C++11 «правило одного определения» нарушалось при инициализации членов класса нестатических и неконстантных переменных. Почему?
Что произойдет, если я вызову allocate_at_least(0) согласно стандарту C++23?