Не определено ли в C99 прокалывание типов через объединение, и стало ли оно определено в C11?

Несколько ответов на вопрос о переполнении стека Получение IEEE Single- биты точности для числа с плавающей запятой предлагают использовать структуру union для обозначения типов (например: преобразование битов float в uint32_t):

union {
    float f;
    uint32_t u;
} un;
un.f = your_float;
uint32_t target = un.u;

Однако значение члена uint32_t объединения, по-видимому, не указано в соответствии со стандартом C99 (по крайней мере, проект n1124), где в разделе 6.2.6.1.7 говорится:

Когда значение хранится в члене объекта типа union, байты представления объекта, которые не соответствуют этому члену, но соответствуют другим членам, принимают неопределенные значения.

По крайней мере, одна сноска к черновику C11 n1570, кажется, подразумевает, что это уже не так (см. Сноску 95 в 6.5.2.3):

Если член, используемый для чтения содержимого объекта объединения, не совпадает с членом, последним использовавшимся для хранения значения в объекте, соответствующая часть объектного представления значения переинтерпретируется как представление объекта в новом типе как описан в 6.2.6 (процесс, иногда называемый «типовой каламбур»). Это может быть ловушка.

Однако текст раздела 6.2.6.1.7 в проекте C99 такой же, как и в проекте C11.

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


person sfstewman    schedule 24.07.2012    source источник
comment
Техническое примечание: доступ к члену объединения, отличному от последнего сохраненного, не приводит к нарушению программой стандарта C. Доступ к такому члену объединения приводит к неопределенному значению (не неопределенному поведению) и, согласно C 1999 4 3, «должен быть правильной программой и действовать в соответствии с 5.1.2.3». Кроме того, компилятор может предоставить дополнительные гарантии относительно значения и оставаться соответствующей реализацией.   -  person Eric Postpischil    schedule 25.07.2012
comment
В основном то, что сказал Вуг. Изменение заключается в том, что C99 нигде явно не упоминает, что чтение членов, кроме последнего записанного, нормально, в то время как C11 (по крайней мере, черновик n1570) делает. Таким образом, «Неопределенное поведение» иначе обозначается в этом международном стандарте словами «неопределенное поведение» или отсутствием какого-либо явного определения поведения. иногда говорилось, что это неопределенное поведение. Я недостаточно разбираюсь в языковом юристе, чтобы вынести окончательный вердикт этой интерпретации.   -  person Daniel Fischer    schedule 25.07.2012
comment
Это не недавнее дополнение, но уже есть в n1256. И это было изменение в результате отчета о дефекте: намерение всегда было тем, что выражается сейчас.   -  person Jens Gustedt    schedule 25.07.2012
comment
@EricPostpischil: изменено нарушение как неопределенное поведение. Решает ли это вашу озабоченность?   -  person sfstewman    schedule 25.07.2012
comment
@sfstewman, неопределенного поведения не существует. Есть только неопределенные значения. Здесь неопределенные значения - это те байты, которые расширяют тип, в который вы пишете, если таковые имеются.   -  person Jens Gustedt    schedule 25.07.2012
comment
@Daniel Fischer: C 1999 действительно говорит, что чтение члена, кроме последнего написанного, «нормально». В нем говорится, что это приводит к неуказанному значению (6.2.6.1), и, согласно моему примечанию выше, это правильная программа (если в остальном она верна). Здесь нет неопределенного поведения. (Неопределенные значения не являются неопределенным поведением: неопределенное поведение не ограничивается стандартом. Для неопределенных значений; стандарт налагает ограничения: поведение должно быть таким, как если бы выражение имеет какое-то значение.)   -  person Eric Postpischil    schedule 25.07.2012
comment
@DanielFischer: В черновиках n1124 и n1570 явно указано, что они не указаны: значение члена объединения, отличное от последнего, сохраненное в (6.2.6.1) в Приложении J (проблемы переносимости). На мой взгляд, это означает, что может существовать компилятор C99 (или C11), в котором использование объединения для обозначения типов не дает того, чего мы ожидаем.   -  person sfstewman    schedule 25.07.2012
comment
@JensGustedt: «Неопределенное поведение» определено в 3.4.4 и означает использование неопределенного значения или другого поведения, которое может иметь более одной возможности.   -  person Eric Postpischil    schedule 25.07.2012
comment
Прочтите его еще раз, там говорится, что те байты, которые соответствуют другому члену, а не тому, который был записан, имеют неопределенное значение. Это означает, что байты, соответствующие этому члену (то есть те, которые являются общими для обоих), имеют определенное значение, а именно то, которое было записано. Этот пункт нужен только для объяснения того, что происходит (или нет) с незаписанными байтами, вот и все.   -  person Jens Gustedt    schedule 25.07.2012
comment
@sfstewman, приложение J не является нормативным.   -  person Jens Gustedt    schedule 25.07.2012
comment
@EricPostpischil Я не вижу здесь явного упоминания о чтении участника. Он говорит, что байты, не соответствующие последнему записанному члену, но другим членам, имеют неопределенные значения. Это признак того, что чтение других членов - это нормально, и это может привести к неопределенному значению, но явно не указано, что это разрешено. В n1570 это явно указано (но, сноски не являются нормативными, так что можно возразить).   -  person Daniel Fischer    schedule 25.07.2012
comment
@sfstewman Как сказал Йенс, до тех пор, пока член, который вы читаете, не использует байты вне объектного представления последнего сохраненного члена, в сноске прямо говорится, что вы получаете байты от последнего сохраненного члена.   -  person Daniel Fischer    schedule 25.07.2012
comment
@DanielFischer: Во-первых, как указывает Йенс Густедт, чтение члена, отличного от написанного, не является неопределенным поведением, если он имеет тот же размер. Согласно 6.2.6.1 2, либо стандарт, либо реализация должны определять количество, порядок и кодировку байтов объекта. Итак, в любом отдельном случае, когда вы пишете один член и читаете другой член того же размера, возможно только одно значение. Во-вторых, мы узнаем из 6.2.6.1, что чтение члена большего размера приводит к неопределенному значению из байтов, которые не являются частью первоначально записанного члена. Затем 41 говорит нам, что это ...   -  person Eric Postpischil    schedule 25.07.2012
comment
по-прежнему является правильной программой, если ничто иное в ней не делает ее неправильной.   -  person Eric Postpischil    schedule 25.07.2012
comment
@EricPostpischil Я не согласен. Но поскольку в старом стандарте никогда явно не говорилось, что происходит при чтении из члена, отличного от последнего сохраненного, некоторые люди говорили, что это неопределенное поведение из-за отсутствия определения поведения. Я часто это читал.   -  person Daniel Fischer    schedule 25.07.2012
comment
@DanielFischer Действительно, все, что не определено явно, не определено по определению. Вопрос тонкий, поскольку стандарт не является официальным документом.   -  person curiousguy    schedule 12.11.2013
comment
@EricPostpischil: Если бы между записью первого значения и чтением второго код проверял байты, занятые полем объединения, стандарт указывал бы, что эти байты должны содержать. Я не знаю, что что-то в старом стандарте помешало бы компилятору, например. оптимизация float в объединении с регистром FPU и его наложением int на регистр ЦП, а также чтение / запись этих регистров в / из памяти только тогда, когда это принудительно char* правилами наложения имен.   -  person supercat    schedule 09.07.2014
comment
См. Также: Переносимость использования union для преобразования.   -  person Gabriel Staples    schedule 03.05.2021


Ответы (4)


Поведение набора текста с помощью union изменено с C89 на C99. Поведение в C99 такое же, как в C11.

Как отметил в своем ответе Вуг, в C99 / C11 допускается использование каламбура. Неуказанное значение, которое может быть ловушкой, считывается, когда члены объединения имеют разный размер.

Сноска была добавлена ​​в C99 после того, как Клайв Д.У. Перышко Отчет о дефекте № 257:

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

[...]

Чтобы решить проблему «набирать текст», добавьте новую сноску 78a к словам «именованный член» в 6.5.2.3 # 3: 78a Если член, используемый для доступа к содержимому объекта объединения, не совпадает с последним членом используется для хранения значения в объекте, соответствующая часть объектного представления значения переинтерпретируется как представление объекта в новом типе, как описано в 6.2.6 (процесс, иногда называемый «каламбуром типов»). Это может быть ловушка.

Формулировка Clive D.W. Feather был принят в качестве Технического исправления в ответе Комитета C на Отчет о дефекте № 283.

person ouah    schedule 24.07.2012
comment
DR неясен, а сноска не является нормативной и может объяснить только то, что определено в другом месте. Также ДР толком ничего не проясняет. Проблема запуталась, потому что WG запуталась. (Кроме того, Вуг ошибается в значении каламбура.) - person curiousguy; 12.11.2013
comment
Цитируемый текст, похоже, не подтверждает ваш вывод. Это может быть представление ловушки. говорится. - person andrewrk; 05.08.2016
comment
@andrewrk Я пришел к выводу, что каламбур разрешен в C99 и C11. Тот факт, что вы можете прочитать представление прерывания для другого члена после записи в член, не меняет этого вывода. Это означает, что в некоторых системах с некоторыми конкретными значениями вы можете вызывать неопределенное поведение. Аналогично, если вы используете бинарный оператор * с некоторыми конкретными значениями операндов, вы также склонны к неопределенному поведению (целочисленное переполнение со знаком), что не означает, что оператор является UB per se или его нельзя использовать. - person ouah; 06.08.2016
comment
@curiousguy Я считаю, что их выбор прочесть сноску не был лучшим способом прояснить путаницу. Им также следовало изменить 6.2.6.1p7 (в C99), чтобы сделать вещи более ясными и нормативными. - person ouah; 06.08.2016
comment
Вы можете получить представление ловушки, даже если они имеют одинаковый размер. Не уверен, почему вы выбрали его для корпуса другого размера. - person Peter Cordes; 03.10.2016

Исходная спецификация C99 оставила это неопределенным.

Одно из технических исправлений к C99 (кажется, TR2) добавило сноску 82, чтобы исправить эту оплошность:

Если член, используемый для доступа к содержимому объекта объединения, не совпадает с членом, последним использовавшимся для хранения значения в объекте, соответствующая часть объектного представления значения переинтерпретируется как представление объекта в новом типе как описан в 6.2.6 (процесс, иногда называемый «каламбуром»). Это может быть ловушка.

Эта сноска сохраняется в стандарте C11 (это сноска 95 в C11).

person Stephen Canon    schedule 24.07.2012
comment
Я думаю, что мы отметили выше, в комментариях к вопросу, что это определено стандартом C 1999 года для членов того же размера, по крайней мере, до такой степени, что требуется соответствующая реализация для определения информации, достаточной для вывода значения . - person Eric Postpischil; 25.07.2012
comment
@EricPostpischil требуется соответствующая реализация для определения информации, достаточной для вывода значения, где это требуется? - person curiousguy; 12.11.2013
comment
@curiousguy: Как указано в комментариях к этому вопросу, C 1999 6.2.6.1 2 (и тот же абзац в C 2011) указывает количество, порядок и кодировку байтов, которые образуют объекты, либо явно указаны (стандартом), либо определяется реализацией. - person Eric Postpischil; 12.11.2013
comment
@EricPostpischil Спасибо. (И я нахожу этот факт немного удивительным.) - person curiousguy; 12.11.2013
comment
К сожалению, тот, кто это написал, не учел, что отсутствие определенного значения при записи одного члена объединения и чтении другого было необходимо для оправдания поведения компилятора, когда функции передаются указатели на разные члены объекта объединения. Если когда-либо законно взять адрес членов объединения и использовать полученные указатели без предварительного преобразования в char или использования memcpy, ничто в Стандарте не оправдает тот факт, что запись одного члена объединения через указатель и чтение другого часто имеет другое поведение, чем запись и чтение непосредственно члены профсоюзов. - person supercat; 13.05.2016

Это всегда было ненадежно. Как отмечали другие, сноска была добавлена ​​в C99 через техническое исправление. Он гласит:

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

Однако сноски указаны в Предисловии как ненормативные:

Приложения D и F составляют нормативную часть этого стандарта; приложения A, B, C, E, G, H, I, J, библиография и указатель предназначены только для информации. В соответствии с Частью 3 Директив ISO / IEC, это предисловие, введение, примечания, сноски и примеры также предназначены только для информации.

То есть сноски не могут запрещать поведение; они должны только прояснить существующий текст. Это непопулярное мнение, но процитированная выше сноска в этом отношении не соответствует действительности - такое поведение не запрещено в нормативном тексте. Действительно, есть противоречивые разделы, например 6.7.2.1:

... Значение не более одного из членов может быть сохранено в объекте union в любое время

В сочетании с 6.5.2.3 (относительно доступа к членам объединения с помощью оператора.):

Значение - это значение указанного члена.

Т.е. если значение только одного члена может быть сохранено, значение другого члена не существует. Это строго подразумевает, что каламбур типов с помощью объединения не должен быть возможен; доступ к члену дает несуществующее значение. Тот же текст все еще существует в документе C11.

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

Лучшее, что мы можем сделать для ратификации сноски, - это сделать некоторые предположения об определении союза как набора перекрывающихся объектов из 6.2.5:

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

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

Даже если мы проигнорируем 6.7.2.1/6.5.2.3 и допустим, как следует в сноске, чтение любого члена объединения возвращает значение, которое будет представлено содержимым соответствующей области хранения - что, следовательно, позволит использовать каламбур - всегда -проблемное правило строгого псевдонима в 6.5 запрещает (с некоторыми незначительными исключениями) доступ к объекту, отличному от его типа. Поскольку доступ - это (3.1) 〈действие во время выполнения〉 для чтения или изменения значения объекта, и поскольку изменение одного из набора перекрывающихся объектов обязательно изменяет другие, то правило строгого псевдонима потенциально может быть нарушено запись члену союза (независимо от того, читается ли она потом другим или нет).

Например, по формулировке стандарта незаконными являются:

union {
   int a;
   float b;
} u;

u.a = 0; // modifies a float object by an lvalue of type int
int *pa = &u.a;
*pa = 1; // also modifies a float object, without union lvalue involved

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

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

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

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

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

Цитируемый вами раздел:

Когда значение хранится в члене объекта типа union, байты представления объекта, которые не соответствуют этому члену, но соответствуют другим членам, принимают неопределенные значения.

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

person davmac    schedule 18.04.2016
comment
Хм ... § 6.2.5 / 20 гласит что члены профсоюзов пересекаются. Сочетание этого со значением не более одного из членов может быть сохранено в объекте объединения в любое время, что позволяет интерпретировать последний как указание на то, что это перекрывающееся пространство хранения может содержать значение ровно одного члена за раз, что по расширению означает, что все неактивные элементы в то время будут альтернативными представлениями некоторых или всех активных элементов (из-за обращения к одному и тому же перекрывающемуся пространству хранения). - person Justin Time - Reinstate Monica; 15.04.2017
comment
Например, для заданных typedef union { int i; char c; } U; и U u;, если u.i присвоено значение 5, то u.c будет одним из байтов целочисленного литерала 5 из-за совместного использования одного и того же пространства хранения. Таким образом, этот мандат, в соответствии с которым члены профсоюзов накладываются друг на друга, является стержнем, который позволяет интерпретацию, которая поддерживает каламбур. - person Justin Time - Reinstate Monica; 15.04.2017
comment
Кажется, что это не должно быть правильной интерпретацией, но технически она соответствует букве стандарта. - person Justin Time - Reinstate Monica; 15.04.2017
comment
@JustinTime, что по расширению означает, что все неактивные члены в то время будут альтернативными представлениями некоторых или всех активных членов (из-за обращения к одному и тому же перекрывающемуся пространству хранения) - то есть ИМО, экстраполяция, но не определенная логическая необходимость; проблема в том, что на самом деле не указано, что влечет за собой перекрытие, за исключением ненормативной сноски, а концепция объектов как простого представления о хранении в некоторой степени несовместима, например, с. строгое правило псевдонима. - person davmac; 18.04.2017
comment
@JustinTime: в любом случае определение доступа к члену в 6.5.2.3 проблематично. Если значение равно значению указанного члена, а указанный член не имеет сохраненного значения, тогда явно существует проблема. Он не говорит, что значение определяется представлением, хранящимся в объекте, соответствующем члену, что, как мне кажется, необходимо для того, чтобы позволить вашу интерпретацию. Хотя, как я говорю в ответе, предположительно, это именно то, что на самом деле задумано. - person davmac; 18.04.2017
comment
Это очень хороший момент. Я просто предположил, что, говоря, что они пересекаются, это означало, что все они используют пространство общей памяти, достаточно большое для самого большого члена союза, чтобы попытаться найти правила, которые юристы использовали для интерпретации, чтобы поддержать каламбур. Я согласен с тем, что это определенно должно быть написано более четко, если оно предназначено для использования каламбура. - person Justin Time - Reinstate Monica; 18.04.2017
comment
@JustinTime: в C89 при заданном union {T1 v1; T2 v2;} u; поведение как u.v1 = thing1; thing2 = u.v2;, так и T1 *p1=&u.v1; T2 *p2=&u.v2; *p1=thing1; thing2=*p2; будет определяться стандартом в одних и тех же случаях (например, в тех случаях, когда используются совместимые типы или правило общей начальной последовательности), а во всех остальных случаях будет определяться реализацией. Реализации могут иметь возможность обрабатывать такие обращения по-разному в зависимости от используемой формы lvalue, но в C89 ничего подобного не делается. C89 не может разрешить перенаправление текста в первом случае, не сделав то же самое во втором. - person supercat; 30.09.2017

Однако это, похоже, нарушает стандарт C99 (по крайней мере, проект n1124), где в разделе 6.2.6.1.7 говорится о некоторых вещах. Это поведение действительно не указано в C99?

Нет, ты в порядке.

Когда значение хранится в члене объекта типа union, байты представления объекта, которые не соответствуют этому члену, но соответствуют другим членам, принимают неопределенные значения.

Это касается блоков данных разного размера. Т.е. если у вас есть:

union u
{
    float f;
    double d;
};

и вы присваиваете что-то f, это изменит нижние 4 байта d, но верхние 4 байта будут в неопределенном состоянии.

Союзы существуют в основном для набора текста.

person Wug    schedule 24.07.2012
comment
Союзы не существуют с единственной целью - набирать текст. Союзы существуют, потому что иногда вы хотите сохранить один тип объекта, а затем получить его, а иногда вы хотите сохранить другой тип объекта, а затем получить его. - person Eric Postpischil; 25.07.2012
comment
Это тип каламбура. Из википедии: каламбур - это общий термин для любого метода программирования, который подрывает или обходит систему типов языка программирования, чтобы достичь эффекта, который было бы трудно или невозможно достичь в рамках формального языка - person Wug; 25.07.2012
comment
Объединения существуют с единственной целью - набирать текст Я думаю, что объединения были добавлены в язык скорее для экономии места. - person ouah; 25.07.2012
comment
Нет, каламбур - это запись одного члена и чтение другого. Написание одного члена и чтение одного и того же члена - это не каламбур. Также нет записи одного, чтения его, записи второго члена и чтения второго члена. Когда вы читаете тот же член, который был записан в последний раз, вы не изменили типы, поэтому вы не обошли систему типов. - person Eric Postpischil; 25.07.2012
comment
Я бы сказал, что использование единой контейнерной структуры для хранения произвольных значений разных типов в одном и том же физическом месте подходит независимо от того, интерпретируются ли данные по разным типам в одной ситуации или нет. - person Wug; 25.07.2012
comment
Под каламбуром обычно понимается запись одного типа и чтение одних и тех же битов обратно в качестве другого. Но union обычно используется внутри struct вместе с enum, который указывает, какой тип в настоящее время принадлежит объединению. например интерпретатор может иметь struct value, который может содержать целое число или значение с плавающей запятой, которое может иметь либо .type = T_INT и .u.int_val = 123, либо .type = T_FLOAT и .u.float_val = 4.56. В этом случае вы ожидаете прочитать только тот же тип из .u, который был первоначально написан, и я бы не считал это каламбуром типа. - person Matthew Slattery; 25.07.2012
comment
Я бы сказал, что использование единой контейнерной структуры для хранения произвольных значений разных типов в одном и том же физическом местоположении квалифицируется Извините, но вы не можете определить, что квалифицируется как тип каламбур. Это старая и четко определенная концепция (переосмысление байтов объекта как другого типа). Повторное использование неиспользуемого хранилища для записи объекта другого типа, безусловно, не каламбура! - person curiousguy; 12.11.2013
comment
верхние 4 байта будут в неопределенном состоянии. - на самом деле это будут неопределенные значения, как показано в вашей предыдущей цитате. - person M.M; 26.11.2015
comment
@MatthewSlattery это усложняется с семейством структур sockaddr_, где поле, определяющее тип (s*_family), является частью структуры. - person Alnitak; 05.09.2018