C - неопределенное значение неопределимое?

В соответствии с этим сообщением неопределенное значение:

3.17.2
1 indeterminate value
either an unspecified value or a trap representation

Согласно google, определение неопределенного:

  • Не известно, известно или установлено
  • Оставленный сомнительный; расплывчаты.

В соответствии с литературным определением определяется:

  • может быть определен

Согласно Merriam-webster, для определения (в конкретном контексте):

  • чтобы узнать или прийти к решению по расследованию, рассуждениям или расчетам

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

Или я ошибаюсь? Если да, то почему?

EDIT: Чтобы уточнить, я публикую это в связи с тем, что стало горячим спором с пользователем, который пытался убедить меня, что неопределенное значение неопределимо, о чем я очень сомневаюсь.

EDIT 2: Чтобы уточнить, "определяемым" я не имею в виду стабильное или полезное значение, даже если это значение мусора для неинициализированной памяти, значение этого мусора все еще можно определить. Я имею в виду, что попытка определить, что значение будет по-прежнему давать в некотором значении, а не... никаких действий. Таким образом, это значение должно быть получено из некоторой памяти, выделенной в качестве хранилища для еще неопределенного значения. Я очень сомневаюсь, что компилятор действительно будет использовать генератор случайных чисел только для того, чтобы придумать какое-то произвольное значение.

Ответ 1

Тот факт, что он является неопределенным, не только означает, что он непредсказуем при первом чтении, но также означает, что он не гарантированно стабилен. Это означает, что чтение одной и той же неинициализированной переменной дважды не гарантирует получение того же значения. По этой причине вы не можете "определить" эту ценность, читая ее. (См. DR # 260 для начального обсуждения этого вопроса с 2004 года и DR # 451, подтверждая эту позицию в 2014 году.)

Например, переменной a может быть присвоено занятие регистра CPU R1 с определенным временным интервалом (вместо места памяти). Чтобы установить оптимальный график назначения переменных в регистры, концепция уровня жизни объекта на уровне языка недостаточно точна. Регистры CPU управляются оптимизирующим компилятором, основанным на гораздо более четкой концепции "стоимости жизни". Время жизни значения начинается, когда переменной присваивается определенное значение. Время жизни значения заканчивается, когда ранее назначенное определенное значение считывается в последний раз. Значение lifetime определяет временной интервал, в течение которого переменная связана с конкретным регистром процессора. Вне этого назначенного таймфрейма один и тот же регистр R1 может быть связан с совершенно другой переменной b. Попытка прочитать неинициализированную переменную a за пределами ее времени жизни может привести к чтению переменной b, которая может быть активно изменена.

В этом примере кода

{
  int i, j;

  for (i = 0; i < 10; ++i)
    printf("%d\n", j);

  for (j = 0; j < 10; ++j)
    printf("%d\n", 42);
}

компилятор может легко определить, что, хотя время жизни объектов i и j перекрывается, время жизни значения вообще не перекрывается, что означает, что как i, так и j могут быть присвоены одному регистру CPU, Если что-то подобное произойдет, вы можете легко обнаружить, что первый цикл печатает постоянно изменяющееся значение i на каждой итерации. Это совершенно согласуется с тем, что значение j является неопределенным.

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

{
  int i;

  for (i = 0; i < 10; ++i)
    printf("%d\n", <future location of j>);
}

{
  int j;

  for (j = 0; j < 10; ++j)
    printf("%d\n", 42);
}

с переменными i и j, занимающими одно и то же место в памяти в разное время. В этом случае первый цикл может снова закончиться печатью значения i на каждой итерации.

Ответ 2

Два последовательных чтения неопределенного значения могут давать два разных значения. Кроме того, чтение неопределенного значения вызывает поведение undefined в случае ловушечного представления.

В DR # 260, C-комитет написал:

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

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

Ответ 3

Стандарт C90 дал понять, что чтение из неопределенного местоположения было undefined. Более поздние стандарты уже не так понятны (неопределенная память - "либо неопределенное значение, либо представление ловушки" ), но компиляторы все еще оптимизируются таким образом, что это только оправдание, если чтение неопределенного местоположения - это поведение undefined, например, умножение целого числа в неинициализированной переменной на два может привести к нечетному результату.

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

Ответ 4

Мы не можем определить значение неопределенного значения даже при операциях, которые обычно приводят к предсказуемым значениям, таким как умножение на ноль. Значение шаткое в соответствии с новым предложенным языком (см. Править).

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

Этот отчет о дефектах охватывает очень похожий вопрос на ваш вопрос. Три вопроса были адресованы:

  • Может ли неинициализированная переменная с продолжительностью автоматического хранения (типа, у которого нет значений ловушки, адрес которых был принят таким образом, что 6.3.2.1p2 не применяется и который не является изменчивым) изменяет его значение без прямого действия программа?
  • Если ответ на вопрос 1 "да", то как далеко может распространиться такая "нестабильность"?
  • Если "неустойчивые" значения могут передаваться через аргументы функции в вызываемую функцию, может ли вызов функции стандартной библиотеки C проявлять поведение undefined из-за этого?

и предоставил следующие примеры с дополнительными вопросами:

unsigned char x[1]; /* intentionally uninitialized */
printf("%d\n", x[0]);
printf("%d\n", x[0]);

Позволяет ли стандарт разрешить реализацию, чтобы этот код печатал два разные значения? И если это так, если мы вставляем один из следующих три утверждения

x[0] = x[0];
x[0] += 0;
x[0] *= 0;

между объявлением и выводами printf, является ли это поведение все еще разрешено? Или, альтернативно, могут ли эти заявления printf выставлять undefined вместо того, чтобы печатать разумное число.

Предлагаемая резолюция, которая вряд ли сильно изменится, такова:

  • Ответ на вопрос 1 "да", неинициализированное значение в описанных условиях может изменить его значение.
  • Ответ на вопрос 2 заключается в том, что любая операция, выполняемая с неопределенными значениями, будет иметь неопределенное значение в результате.
  • Ответ на вопрос 3 состоит в том, что функции библиотеки будут демонстрировать поведение undefined при использовании для неопределенных значений.
  • Эти ответы подходят для всех типов, у которых нет ловушек.
  • Эта точка зрения подтверждает позицию C99 DR260.
  • Комитет согласен с тем, что эта область выиграет от нового определения чего-то, подобного "шаткому" значению, и что это следует учитывать при любом последующем пересмотре этого стандарта.
  • Комитет также отмечает, что байты заполнения внутри структур, возможно, представляют собой отличную форму "шаткого" представления.

Обновление для редактирования адреса

Часть обсуждения включает этот комментарий:

  • Сильные настроения, частично основанные на предыдущем опыте разработки Приложения L, требуют новой категории "шаткой" ценности. Основная проблема заключается в том, что современные компиляторы отслеживают значение дорожного распространения, а неинициализированные значения, синтезированные для первоначального использования объекта, могут быть отброшены как несущественные до синтеза другого значения для последующего использования. Требовать, чтобы в противном случае была поражена важная оптимизация компилятора. Все использование "шатких" значений может считаться поведением undefined.

Таким образом, вы сможете определить значение, но значение может измениться при каждой оценке.

Ответ 5

Когда стандарт вводит термин, такой как неопределенный, это нормативный термин: применяется стандартное определение, а не определение словаря. Это означает, что неопределенное значение не больше или меньше неопределенного значения или представления ловушки. Обычные английские значения неопределенного значения не применимы.

Даже те термины, которые не определены в стандарте, могут быть нормативными, путем включения нормативных ссылок. Например, раздел 2 стандарта C99 нормативно включает документ ISO/IEC 2382-1:1993, Information Technology - Vocabulary - Part 1: Основные термины.

Это означает, что если термин используется в стандарте и не определен в тексте (не вводится курсивом и не излагается, а не указан в разделе терминов), он может, тем не менее, быть словом из вышеупомянутого документа лексики; в этом случае применяется определение из этого стандарта.