Использует структуру без всех членов, назначенных undefined?

Рассмотрим этот код в области блока:

struct foo { unsigned char a; unsigned char b; } x, y;
x.a = 0;
y = x;

C [N1570] 6.3.2.1 2 говорит: "Если lvalue обозначает объект с автоматическим временем хранения, который мог быть объявлен с классом хранения register (никогда не был принят его адрес), и что объект не инициализирован (не объявлен с инициализатором, и до его использования не выполнялось присвоение), поведение undefined."

Хотя члену x присвоено значение, не было присвоено значение x, и его адрес не был выполнен. Таким образом, оказывается, что 6.3.2.1 2 показывает, что поведение x в y = x равно undefined.

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

(1) Есть ли что-нибудь в стандарте, которое, строго говоря, вызывает 6.3.2.1 2 не применять (make undefined) код выше?

(2) Предположим, мы модифицировали стандарт или определяли разумную модификацию 6.3.2.1 2, есть ли причины предпочесть одно из следующего за другими? (а) 6.3.2.1 2 не применяется к структурам. (b) Если хотя бы одному элементу структуры присвоено значение, структура не является неинициализированной для целей 6.3.2.1 2. (c) Если все названные элементы структуры 1 были присвоенное значение, структура не является неинициализированной для целей 6.3.2.1 2.

Сноска

1 Структуры могут иметь неназванные элементы, поэтому не всегда можно назначить значение каждому члену структуры. (Члены-члены имеют неопределенное значение, даже если структура инициализирована, на 6.7.9 9.)

Ответ 1

Я считаю, что это поведение undefined просто потому, что оно не определено явно стандартом. От 4 Соответствие §2 (подчеркните мое):

... Неопределенное поведение в противном случае указанном в этом Международном стандарте словами "undefined поведение ) или отсутствие явного определения поведения.

После многих чтений в проекте N1570 я не могу найти никакого явного определения поведения для использования частично инициализированной структуры. С одной стороны, 6.3.2.1 §2 говорит:

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

поэтому здесь x является автоматическим, никогда не инициализируется (только один из его членов), и, с признательностью, его адрес никогда не принимается, поэтому мы можем думать, что он явно UB

С другой стороны, 6.2.6.1 §6 гласит:

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

Как 6.2.6.1 §5 только что определил ловушечное представление:

Определенные представления объектов не обязательно должны представлять значение типа объекта. Если сохраненный значение объекта имеет такое представление и считывается выражением lvalue, которое делает не имеют характера, поведение undefined. Если такое представление создается побочным эффектом, который изменяет всю или любую часть объекта с помощью выражения lvalue, которое означает значение 0 для члена a и значение undefined для члена b. не имеет типа символа, поведение undefined.50) Такое представление называется представление ловушки.

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

Кроме того, для меня неясно, устанавливает ли значение элемента структуры фактически структура в унифицированном состоянии.

По всем этим причинам я считаю, что стандарт не определяет, каково должно быть поведение, и просто по этой причине это поведение undefined.


Я уверен, что любой обычный компилятор примет его и даст y текущее представление x, что означает значение 0 для члена a и неопределенное значение того же представления, что и текущий один для x.b для члена b.

Ответ 2

Во-первых, отметим, что цитированная часть 6.3.2.1/2, так называемое "предложение Itanium" - это единственное условие, при котором этот код может иметь проблему. Другими словами, если этот пункт не присутствовал, код в порядке. Структуры могут не иметь ловушечных представлений, поэтому y = x; в противном случае ОК, даже если x полностью неинициализирован. Разрешение DR 451 поясняет, что неопределенные значения могут быть переданы путем присвоения, не вызывая UB.


Вернемся к предложению Itanium. Как вы отмечаете, в Стандарте четко не указано, отменяет ли x.a = 0; предварительное условие "x неинициализировано".

IMO, это означает, что мы должны обратиться к обоснованию положения Itanium, чтобы определить намерение. Цель формулировки стандартного документа, в общем, заключается в осуществлении намерения; вообще говоря, я не согласен с догматикой о мельчайших деталях стандарта: принятие оттенков смысла из формулировки, которые не были предназначены тем, кто создал формулировку.

Этот Q/A дает хорошее объяснение обоснования. Потенциальная проблема заключается в том, что x может храниться в регистре с установленным битом NaT, а затем y = x вызовет аппаратное исключение из-за чтения регистра, который имеет этот бит.


Итак, вопрос: на IA64 делает x.a = 0; очистить бит NaT? Я не знаю, и я думаю, нам нужен кто-то, знакомый с этой платформой, чтобы дать здесь окончательный ответ.

Наивно, я полагаю, что если x находится в регистре, то, вообще говоря, x.a = 0; нужно будет прочитать старое значение и применить маску для очистки битов для a, тем самым вызывая исключение, если x был NaT. Однако x.a = 0; не может инициировать UB, поэтому логика должна быть неправильной. Возможно, IA64-компиляторы никогда не сохраняют структуру в регистре или, возможно, они очищают бит NaT при объявлении одного или, возможно, есть аппаратная инструкция для реализации x.a = 0; в ранее-NaT-регистре, я не знаю.

Ответ 3

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

К сожалению, авторы Стандарта используют одну и ту же терминологию для описания двух ситуаций:

  • Некоторые реализации определяют поведение какого-либо действия X во всех случаях, а некоторые определяют его только для некоторых; другие части Стандарта определяют действие в нескольких случаях. Авторы хотят сказать, что реализации не должны вести себя так же, как те, которые определяют поведение во всех случаях, не отменяя гарантий, сделанных в другом месте Стандартного

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

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

С практической точки зрения определение поведения копирования структуры как копирования состояния (определенного или неопределенного) всех полей не будет стоить больше, чем позволять компиляторам вести себя произвольно при копировании частично написанных структур. К сожалению, некоторые авторы компиляторов ошибочно полагают, что "умность" и "глупость" являются антонимами и, таким образом, ведут себя так, как будто авторы Стандарта хотели пригласить компиляторов предположить, что программы никогда не получат никаких данных, которые могли бы привести к копированию структур после того, были частично записаны.