Является ли поведение следующего шаблона доступа C union undefined?

Следующее не является неопределенным поведением в современном C:

union foo
{
    int i;
    float f;
};
union foo bar;
bar.f = 1.0f;
printf("%08x\n", bar.i);

и печатает шестнадцатеричное представление 1.0f.

Однако следующее поведение undefined:

int x;
printf("%08x\n", x);

Как насчет этого?

union xyzzy
{
    char c;
    int i;
};
union xyzzy plugh;

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

printf("%08x\n", plugh.i);

Но как насчет этого. Это неопределенное поведение или нет?

plugh.c = 'A';
printf("%08x\n", plugh.i);

Большинство компиляторов C в настоящее время будут иметь sizeof(char) < sizeof(int), при этом sizeof(int) будет либо 2, либо 4. Это означает, что в этих случаях будет записано не более 50% или 25% plugh.i, но при чтении оставшихся байтов будут считываться неинициализированные данные, и следовательно, должно быть неопределенное поведение. На основании этого, является ли все поведение неопределенным при чтении?

почему int x; printf("%08x\n", x); УБ? Приведение int к беззнаковому int - это определенное поведение, а отсутствие инициализации переменной - это не UB, так почему этот код попадает в UB?

Tom's 12.09.2018 10:15

@ Tom's - Доступ к неопределенным значениям - UB. В упор.

StoryTeller - Unslander Monica 12.09.2018 10:16

@ Tom's В этой строке нет приведений, и почему вы думаете, что использование неинициализированной переменной - это не UB?

melpomene 12.09.2018 10:16

Поскольку поведение определено ... Хорошо, я согласен, что этот код в конечном итоге будет печатать значение "мусор / случайное", но он никогда не выйдет из строя, ведется по-другому. И есть "приведение", хотя оно действительно неявно: printf% x ждет беззнаковое int, а было дано int?

Tom's 12.09.2018 10:18

@ Tom's - "но он никогда не выйдет из строя, ведут себя по-другому" Стандарт C, о котором идет речь, не гарантирует ничего подобного. В этом весь смысл неопределенного поведения.

StoryTeller - Unslander Monica 12.09.2018 10:19

Приведение @ Tom's A - это явное преобразование типа. Здесь нет актеров. Нет даже неявного преобразования, потому что varargs не дает вам контекста известного типа. Или вы думаете, что printf("%f", 42) в порядке, потому что 42 можно неявно преобразовать в double?

melpomene 12.09.2018 10:21

@StoryTeller Странно. Я не понимаю, как унифицированная переменная (которая не является указателем) может вызывать другое поведение. Спасибо за информацию.

Tom's 12.09.2018 10:22

Я не уверен, что это дубликат. Во втором отрывке цитируемого текста в ответе Шафика Ягмура отмечается, что «... соответствующая часть объектного представления значения переинтерпретируется как объектное представление в новом типе ...». Проблема здесь в том, что в char недостаточно битов для создания int, поэтому обычно 8 или 24 бита int будут неинициализированными. Как отметил @StoryTeller, унифицированный доступ - это UB.

dgnuff 12.09.2018 10:23

@ Tom's stackoverflow.com/questions/6725809/trap-presentation

melpomene 12.09.2018 10:24

@melpomene, спасибо, я это прочту.

Tom's 12.09.2018 10:28

@dgnuff Возможно, из общей вежливости вы захотите принять один из исчерпывающих ответов ниже?

Bathsheba 12.09.2018 12:20

«Следующее - это не UB в современном C:» никто не сказал, что на самом деле первый пример полностью UB?

Stargateur 12.09.2018 18:01

@Stargateur Хотя в C формулировка сильно изменилась чтение неопределенного значения является неопределенным поведением с некоторыми оговорками. К счастью для C++, C++ 14 решил это более лаконично

Shafik Yaghmour 12.09.2018 18:15

@ShafikYaghmour Зачем заставлять язык определять то, что никогда не должно быть написано, люди не предполагают использовать подобный союз, и я полностью согласен. Если OP хочет посмотреть на байт числа с плавающей запятой, это отлично определен. Плюс просто нет доказательств того, что int имеет тот же размер, что и float.

Stargateur 12.09.2018 18:25

Воспроизведение типа @Stargateur через объединение хорошо определено в C, хотя я бы просто использовал memcpy и bit_cast в C++, см. мой ответ здесь для более подробной информации. Лично я чувствую, что союзы предназначены для вариантных типов, но эта лодка давно ушла.

Shafik Yaghmour 12.09.2018 18:27

@ShafikYaghmour Нет, это не так! И все, что не подталкивает людей к этому

Stargateur 12.09.2018 18:31

@Stargateur: если программа хочет извлечь экспоненту из double, уровень сложности компилятора, необходимый для поддержки чтения и записи наложенных uint16_t или uint32_t, будет намного меньше уровня сложности компилятора, необходимого для распознавания всех разумных шаблонов кода, с помощью которых программа может собрать байты числа с плавающей запятой в более длинный целочисленный тип, а затем разложить этот тип на последовательность байтов и преобразовать эти шаблоны в одно 16- или 32-битное чтение и одну такую ​​запись.

supercat 12.09.2018 19:13

@Stargateur "Доступ к неопределенным значениям - UB" - неверно

M.M 13.09.2018 02:18
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
18
576
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

Отчет о дефекте 283: Доступ к несуществующему члену профсоюза («набирать текст») покрывает это и сообщает нам, что существует неопределенное поведение, если есть представление ловушки.

В отчете о дефектах спрашивали:

In the paragraph corresponding to 6.5.2.3#5, C89 contained this sentence:

With one exception, if a member of a union object is accessed after a value has been stored in a different member of the object, the behavior is implementation-defined.

Associated with that sentence was this footnote:

The "byte orders" for scalar types are invisible to isolated programs that do not indulge in type punning (for example, by assigning to one member of a union and inspecting the storage by accessing another member that is an appropriately sixed array of character type), but must be accounted for when conforming to externally imposed storage layouts.

The only corresponding verbiage in C99 is 6.2.6.1#7:

When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values, but the value of the union object shall not thereby become a trap representation.

It is not perfectly clear that the C99 words have the same implications as the C89 words.

В отчет о дефектах добавлена ​​следующая сноска:

Attach a new footnote 78a to the words "named member" in 6.5.2.3#3:

78a If the member used to access the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called "type punning"). This might be a trap representation.

C11 6.2.6.1 Общие сообщает нам:

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.50)Such a representation is called a trap representation.

@PotentialUpvoters, почему это только +2? Я внесу свою лепту.

Bathsheba 12.09.2018 12:19

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

Shafik Yaghmour 12.09.2018 18:24

Из 6.2.6.1 §7:

When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values.

Таким образом, значение plugh.i не будет указано после установки plugh.c.

Из сноски к 6.5.2.3 §3:

If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type punning’’). This might be a trap representation.

Это говорит о том, что каламбур специально разрешен (как вы утверждали в своем вопросе). Но это может привести к представлению ловушки, и в этом случае чтение значения имеет неопределенное поведение в соответствии с 6.2.6.1 §5:

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined. 50) Such a representation is called a trap representation.

Если это не представление ловушки, кажется, что в стандарте нет ничего, что могло бы сделать это поведение undefined, потому что из 4 §3 мы получаем:

A program that is correct in all other aspects, operating on correct data, containing unspecified behavior shall be a correct program and act in accordance with 5.1.2.3.

C11 §6.2.6.1 p7 говорит:

When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values.

Итак, plugh.i не указан.

Другие ответы касаются основного вопроса о том, вызывает ли чтение plugh.i неопределенное поведение, когда plugh не был инициализирован и когда-либо был назначен только plugh.c. Вкратце: нет, если байты plugh.i не составляют представление прерывания во время чтения.

Но я хочу обратиться непосредственно к предварительному утверждению в вопросе:

Most C compilers nowadays will have sizeof(char) < sizeof(int), with sizeof(int) being either 2 or 4. That means that in these cases at most 50% or 25% of plugh.i will have been written to

Вопрос, похоже, предполагает, что присвоение значения plugh.c оставит нетронутыми те байты plugh, которые не соответствуют c, но стандарт никоим образом не поддерживает это предположение. Фактически, он прямо отрицает любую такую ​​гарантию, поскольку, как отмечали другие:

When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values.

(C2011, 6.2.6.1/7; курсив мой)

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

Я думаю, что концепция шаткие ценности охватывает эту проблему.

Shafik Yaghmour 12.09.2018 18:39

@ShafikYaghmour: Есть некоторые ситуации, когда гарантия того, что запись в член структуры не повлияет на какое-либо хранилище за пределами этого члена, была бы полезна, а работа с отсутствием такой гарантии была бы дорогостоящей; есть и другие, где такая гарантия была бы дорогостоящей. Есть аналогичные платформы, где такая гарантия может быть поддержана почти бесплатно, и другие, где это будет дорого. Я не думаю, что авторы Стандарта намеревались применить указанную выше лицензию, за исключением случаев, когда разработчик решил, что последние расходы превысят первые для ...

supercat 12.09.2018 19:07

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

supercat 12.09.2018 19:08

@ShafikYaghmour, насколько я понимаю, что этот термин должен означать, я не думаю, что «шаткие значения» необходимы для понимания того, что я описываю. То есть, являются ли неопределенные значения, принимаемые некоторыми байтами plugh.i при назначении на plugh.c, шаткими, - это дополнительное, отдельное рассмотрение.

John Bollinger 12.09.2018 19:20

@JohnBollinger: В некоторых случаях единственный способ достичь оптимальной производительности - это распознать концепцию «шатких байтов». Предположим, например, что этот код записывает unionArray[i].struct1.member2, unionArray[j].struct2 и unionArray[i].struct1.member1 в этом порядке, а затем возвращает unionArray[i].struct1. Я думаю, что эта последовательность должна иметь определенное поведение, если ничего, кроме первого члена возвращаемой структуры или unionArray[i].struct1, когда-либо не проверяется, и не вижу оснований для такой проверки через тип символа для вызова UB, но не вижу никакой основы, кроме «шатких значений» для. ..

supercat 12.09.2018 20:21

... возвращаемая структура не должна соответствовать окончательному значению unionArray[i].struct1, если только возврат или наблюдение не вызывает UB, или не действует какое-то «шаткое значение».

supercat 12.09.2018 20:22

@supercat, я согласен с тем, что в вашем сценарии нет никакого оправдания, кроме шатких значений (которые, похоже, не вошли в C17) или UB, как вы описываете, для значения структуры, возвращаемого функцией, чтобы отличаться от окончательного значения unionarray[i].struc1. Однако вопрос, который я рассматриваю в этом ответе, заключается в том, что стандарт говорит о том, что это за значение в первую очередь, в случае, если i == j. Без каких-либо других подробностей все, что я могу с уверенностью сказать, это то, что его member1 будет иметь значение, установленное в третьей операции.

John Bollinger 12.09.2018 21:01

@JohnBollinger: Стандарт явно не требует, чтобы неиспользуемые члены принимали какой-либо конкретный битовый шаблон. Неясно, должны ли они вести себя так, как если бы они содержали согласованный битовый шаблон некоторые. Думаю, я придумал упрощенный пример, который предлагаю в своем ответе.

supercat 13.09.2018 01:31

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

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

struct c8 { uint32_t u; unsigned char arr[4]; };
union uc { uint32_t u; struct c8 dat; } uuc1,uuc2;

void wowzo(void)
{
  union uc u;
  u.u = 123;
  uuc1 = u;
  uuc2 = u;
}

Я думаю, ясно, что Стандарт не требует, чтобы байты в uuc1.dat.arr или uuc2.dat.arr содержали какое-либо конкретное значение, и что компилятору будет разрешено для каждого из четырех байтов i == 0..3 копировать uuc1.dat.arr[i] в uuc2.dat.arr[i], скопируйте uuc2.dat.arr[i] в uuc1.dat.arr[i] или запишите как uuc1.dat.arr[i], так и uuc2.dat.arr[i] с совпадающими значениями. Я не думаю, что ясно, намеревается ли Стандарт требовать, чтобы компилятор выбирал один из этих способов действий, а не просто оставлял эти байты удерживающими то, что они случайно содержат.

Очевидно, что код должен иметь поведение, определенное от корки до корки, если ничто никогда не наблюдает за содержимым uuc1.dat.arr или uuc2.dat.arr, и нет никаких оснований предполагать, что проверка этих массивов должна вызывать UB. Кроме того, не существует определенных средств, с помощью которых значение u.dat.arr могло бы изменяться между назначениями uuc1 и uuc2. Это предполагает, что uuc1.dat.arr и uuc2.dat.arr должны содержать совпадающие значения. С другой стороны, для некоторых типов программ сохранение явно бессмысленных данных в uuc1.dat.arr и / или uuc1.dat.arr редко служит какой-либо полезной цели. Я не думаю, что авторы Стандарта специально намеревались требовать таких хранилищ, но заявление о том, что байты принимают «неуказанные» значения, делает их необходимыми. Я бы ожидал, что такая поведенческая гарантия будет устаревшей, но я не знаю, что могло бы ее заменить.

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