Является ли следующий шаблон доступа C union неопределенным поведением? - PullRequest
0 голосов
/ 12 сентября 2018

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

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

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

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

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, но при чтении остальные байты будут считывать неинициализированные данные и, следовательно, должны иметь неопределенное поведение. Исходя из этого, является ли все поведение чтения неопределенным?

Ответы [ 5 ]

0 голосов
/ 12 сентября 2018

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

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

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 редко будет служить какой-либо полезной цели. Я не думаю, что авторы Стандарта специально намеревались требовать таких хранилищ, но утверждение, что байты принимают значения «Unspecified», делает их необходимыми. Я ожидаю, что такая гарантия поведения будет устарела, но я не знаю, что может заменить ее.

0 голосов
/ 12 сентября 2018

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

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

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

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

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

( C2011, 6.2.6.1/7; выделение добавлено)

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

0 голосов
/ 12 сентября 2018

Из 6.2.6.1 §7:

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

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

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

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

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

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

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

Программа, правильная во всех других аспектах, работающая с правильными данными, содержащая неопределенное поведение, должна быть правильной программой и действовать в соответствии с 5.1.2.3.

0 голосов
/ 12 сентября 2018

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

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

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

0 голосов
/ 12 сентября 2018

Отчет о дефекте 283: Доступ к нетоковому члену объединения ("type punning") охватывает это и говорит нам, что при наличии представления ловушек существует неопределенное поведение.

Отчет о дефектеспросил:

В параграфе, соответствующем 6.5.2.3 # 5, C89 содержал это предложение:

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

С этим предложением была связана эта сноска:

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

Единственное соответствующее слово в C99 - это 6.2.6.1 # 7 :

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

Не совсем ясно, что слова C99 имеют те же значения, что и слова C89.

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

Присоединить новую сноску 78a к словам «именованный элемент» в 6.5.2.3 # 3:

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

C11 6.2.6.1 Общее говорит нам:

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

...