Является ли a или aa неопределенным поведение, если a не инициализировано?

Рассмотрим эту программу:

#include  int main(void) { unsigned int a; printf("%u %u\n", a^a, aa); return 0; } 

Это неопределенное поведение?

На первый взгляд, a – неинициализированная переменная. Таким образом, это указывает на неопределенное поведение. Но a^a и aa равны 0 для всех значений a , по крайней мере, я думаю, это так. Возможно ли, что есть какой-то способ утверждать, что поведение хорошо определено?

В C11:

  • Он явно не определен в соответствии с 6.3.2.1/2, если никогда не был принят его адрес (приведено ниже)
  • Это может быть ловушечное представление (которое вызывает UB при доступе). 6.2.6.1/5:

Определенные представления объектов не должны представлять значение типа объекта.

Unsigned ints может иметь ловушечные представления (например, если у него есть 15 прецизионных бит и 1 бит четности, доступ к a может вызвать ошибку четности).

6.2.4 / 6 гласит, что начальное значение является неопределенным, а определение под 3.19.2 является либо неопределенным значением, либо ловушечным представлением .

Далее: в C11 6.3.2.1/2, как указано Паскалем Куоком:

Если lvalue обозначает объект с продолжительностью автоматического хранения, который мог быть объявлен с classом хранения регистров (никогда не был принят его адрес), и этот объект не инициализирован (не объявлен с инициализатором, и его назначение не было выполнено до использования ), поведение не определено.

Это не имеет исключения для типов символов, поэтому этот пункт, как представляется, заменяет предыдущее обсуждение; доступ к x сразу не определен, даже если представления ловушки не существуют. Этот раздел был добавлен в C11 для поддержки процессоров Itanium, которые действительно имеют состояние ловушки для регистров.


Системы без ловушечных представлений: Но что, если мы запишем &x; так что возражение 6.3.2.1/2 больше не применяется, и мы находимся в системе, которая, как известно, не имеет ловушечных представлений? Тогда значение является неопределенным значением . Определение неопределенного значения в 3.19.3 немного расплывчато, однако оно разъяснено DR 451 , которое заключает:

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

Согласно этой резолюции, int a; &a; int b = a - a; int a; &a; int b = a - a; приводит к тому, что b имеет неопределенное значение.

Обратите внимание: если неопределенное значение не передается библиотечной функции, мы все еще находимся в области неопределенного поведения (не неопределенное поведение). Результаты могут быть странными, например, if ( j != j ) foo(); может вызвать foo, но демоны должны оставаться в полости носа.

Да, это неопределенное поведение.

Во-первых, любая неинициализированная переменная может иметь «разбитое» (или «ловушечное») представление. Даже одна попытка доступа к этому представлению вызывает неопределенное поведение. Более того, даже объекты типов без захвата (например, unsigned char ) могут по-прежнему приобретать специальные зависящие от платформы состояния (например, NaT – Not-A-Thing – на Itanium), которые могут появляться как проявление их «неопределенного значения».

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

Если объект имеет автоматическую продолжительность хранения и его адрес не берется, попытка его чтения будет давать Undefined Behavior. Принимая адрес такого объекта и используя указатели типа «unsigned char» для считывания их байтов, гарантируется Стандартом, чтобы получить значение типа «unsigned char», но не все компиляторы придерживаются Стандарта в этом отношении , ARM GCC 5.1, например, при предоставлении:

  #include  #include  struct q { uint16_t x,y; }; volatile uint16_t zz; int32_t foo(uint32_t x, uint32_t y) { struct q temp1,temp2; temp1.x = 3; if (y & 1) temp1.y = zz; memmove(&temp2,&temp1,sizeof temp1); return temp2.y; } 

будет генерировать код, который будет возвращать x, если y равно нулю, даже если x находится вне диапазона 0-65535. Стандарт дает понять, что беззнаковые символы, считываемые с неопределенным значением, гарантируют, что они будут давать значение в пределах диапазона unsigned char , а поведение memmove определяется как эквивалентное последовательности символов чтения и записи. Таким образом, temp2 должен иметь значение, которое может быть записано в него посредством последовательности записей символов, но gcc решает заменить memmove назначением и игнорировать тот факт, что код принял адрес temp1 и temp2.

Имея средство принуждения компилятора рассматривать переменную как имеющую произвольное значение своего типа, в тех случаях, когда любое такое значение было бы одинаково приемлемым, было бы полезно, но в Стандарте не указывается чистое средство для этого (сэкономьте для хранения определенного значения, которое будет работать, но часто бесполезно медленно). Даже операции, которые должны логически заставлять переменную удерживать значение, которое может быть представлено, поскольку некоторая комбинация битов не может быть использована для работы со всеми компиляторами. Следовательно, ничего не может быть гарантировано в отношении таких переменных.

Давайте будем гением компьютера.