Включение нулевого указателя приводит к тому, что содержимое другого указателя имеет значение (скорее всего, мусор)

В настоящее время я изучаю указатели в c. Я узнал, что если я не назначу адрес указателю и попытаюсь напечатать значение содержимого указателя, программа не будет работать должным образом.

#include <stdio.h>

int main()
{
    double *q;

    printf("Hello World\n");
    printf("*q = %lf", *q);

    return 0;
}

Например, приведенный выше код напечатает «Hello World», а затем вернет значение, отличное от 0, полностью игнорируя вторую функцию printf(). Вот результат.

Однако все становится интереснее, если в код включен нулевой указатель.

#include <stdio.h>

int main()
{
    double *p = NULL, *q;

    printf("Hello World\n");
    printf("*q = %lf", *q);

    return 0;
}

Вот это результат. Честно говоря, этот вывод не имеет для меня никакого смысла. Я ожидал получить тот же результат, что и раньше, но этого явно не произошло. Как включение нулевого указателя может сделать *q = 0,000000? Я очень растерян. Пожалуйста, помогите мне понять, что здесь происходит.

Кроме того, отличается ли вывод от устройства к устройству? У меня есть подозрение, что устройства с ОС Linux будут выдавать другой результат. На моем устройстве в качестве ОС установлена ​​Windows 11. Отличается ли результат и для других пользователей Windows (11 или других)? А может быть, значение 0,000000 — это мусорное значение? Мне любопытно.

Заранее спасибо.

Это неопределенное поведение, вы не можете ожидать каких-либо последовательных результатов.

Thomas Jager 23.08.2024 17:37

Неопределённое поведение не определено. Но может случиться так, что q каким-то образом получит значение, указывающее на местоположение, в котором выделено p. Всё равно фигня, но оказывается ноль.

Eugene Sh. 23.08.2024 17:41

«Я ожидал получить тот же результат, что и раньше…» --> Любые ожидания подразумевают определенный уровень определенного поведения. *q остается неопределенным поведением или, может быть, неопределенным.

chux - Reinstate Monica 23.08.2024 17:43

Совет: при исследовании результатов FP, подобных этому, используйте %g, %e или %a, а не %f. Они более информативны, когда значения малы. (например, значение может отличаться от 0,0)

chux - Reinstate Monica 23.08.2024 17:45

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

Some programmer dude 23.08.2024 17:46

@Akib Aftermath, Различия между Linux и Windows: это скорее проблема компилятора, чем ОС.

chux - Reinstate Monica 23.08.2024 17:48

@chux-ReinstateMonica Вероятно, и то, и другое. От того, как инициализируется память для процесса, до того, как обрабатывается доступ к неверному местоположению.

Eugene Sh. 23.08.2024 17:50

@ЕвгенийШ. Я бы сказал, что комментарий уже подразумевает и то, и другое.

chux - Reinstate Monica 23.08.2024 18:23

0xC0000005 — это код NTSTATUS STATUS_ACCESS_VIOLATION.

Ian Abbott 23.08.2024 18:27

Не анализировать УБы Почему?:godbolt.org/z/5rY5zs5Kc

0___________ 23.08.2024 18:43
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Ваша первая программа не вернулась; оно разбилось.

Вы не инициализировали q. В printf("*q = %lf", *q);*q говорит загрузить double из того места, на которое указывает q. Куда указывает q? Мы не знаем, потому что вы не задали значение. Здесь могут произойти самые разные вещи, в том числе:

  • Компилятор сгенерировал инструкции, которые загружают значение из того места, где должно было храниться значение q, а затем использует это загруженное значение в качестве адреса для загрузки double. Это значение, загружаемое для q, может быть адресом, который не отображается в памяти вашего процесса, поэтому оборудование сигнализирует об ошибке памяти, и ваш процесс выходит из строя.
  • Как указано выше, программа загружает значение для q, но оно указывает на адрес, который находится в памяти вашего процесса, но он не выровнен, поэтому оборудование сигнализирует об ошибке выравнивания, и ваш процесс выходит из строя.
  • Как и выше, но адрес представляет собой читаемый выровненный адрес в памяти, поэтому программа загружает оттуда значение, интерпретирует его как double и печатает полученное значение.
  • Во время компиляции компилятор распознает, что q не инициализирован и, следовательно, printf("*q = %lf", *q); не имеет смысла, поэтому он помечает его как код с неопределенным поведением и позволяет оптимизатору исключить его из семантического представления вашей программы. (Это может привести к исчезновению целых частей вашей программы из сгенерированного кода.)
  • Во время компиляции компилятор распознает, что q не инициализирован, и печатает об этом диагностическое сообщение.

Какое из этих или других событий произойдет, зависит от обстоятельств, в том числе от того, включена ли оптимизация в компиляторе, какие предупреждения в компиляторе включены, где ваша программа загружается в память, как компилятор размещает переменные в памяти и более. (В отличие от утверждения некоторых комментариев, утверждение «нельзя ожидать каких-либо последовательных результатов» неверно. То, что вы можете или не можете ожидать, зависит от обстоятельств. Стандарт C не определяет поведение, но это не означает, что поведение на него не влияют другие вещи, о которых вы можете узнать.)

Когда вы изменили программу, вы изменили некоторые из этих факторов, например, то, что оказалось в памяти, из которой программа пыталась загрузить значение q. Если вы перейдете на запуск этого в Linux вместо Windows, вы измените другие факторы, и поэтому результаты могут (и, вероятно, будут) отличаться от системы к системе.

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