C союзы и неопределенное поведение - PullRequest
1 голос
/ 08 марта 2020

В следующем примере кода есть неопределенное поведение или поведение, определяемое реализацией? Могу ли я присвоить значение одному члену объединения и прочитать его обратно из другого?

#include <stdio.h>
#include <stdint.h>

struct POINT
{
    union
    {
        float Position[3];
        struct { float X, Y, Z; };
    };
};

struct INT
{
    union
    {
        uint32_t Long;
        uint16_t Words[2];
        uint8_t Bytes[4];
    };
};

int main(void)
{
    struct POINT p;

    p.Position[0] = 10;
    p.Position[1] = 5;
    p.Position[2] = 2;
    printf("X: %f; Y: %f; Z: %f\n", p.X, p.Y, p.Z);

    struct INT i;

    i.Long = 0xDEADBEEF;
    printf("0x%4x%4x\n", i.Words[0], i.Words[1]);
    printf("0x%2x%2x%2x%2x\n", i.Bytes[0], i.Bytes[1], i.Bytes[2], i.Bytes[3]);

    return 0;
}

Вывод на моем компьютере:

X: 10.000000; Y: 5.000000; Z: 2.000000
0xbeefdead
0xefbeadde

Он печатает слова / байты в наоборот, потому что x86 имеет младший порядок, как и ожидалось.

Ответы [ 3 ]

3 голосов
/ 09 марта 2020

существует ли какое-либо неопределенное или определяемое реализацией поведение?

Некоторые основные проблемы c:

struct { float X, Y, Z; }; может иметь отступ между X, Y, Z рендерингом printf("X: %f; Y: %f; Z: %f\n", p.X, p.Y, p.Z); неопределенное поведение как p.Z, et c. может не инициализироваться.

i.Long = 0xDEADBEEF; printf("0x%4x%4x\n", i.Words[0], i.Words[1]); приводит к поведению, определяемому реализацией, поскольку C не требует определенного endian . (Похоже, OP уже знает об этом.)

Могу ли я присвоить значение одному члену объединения и прочитать его из другого?

Да - в рамках ограничений , Другие ответы хорошо обращаются к этой части.

1 голос
/ 08 марта 2020

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

Стандарт C указывает, что чтение объекта объединения через член, отличный от последний записанный член будет интерпретировать байты, используя новый тип. Однако, если взглянуть на список типов lvalue, которые могут быть использованы для доступа к объектам структуры или объединения, то нет никаких условий для доступа к структурам или объединениям, использующим объекты не символьных типов-членов. В большинстве случаев, когда будет использоваться указатель или lvalue типа-члена, он будет явно заново получен из указателя или lvalue на родительский тип, и если компилятор предпримет какие-либо разумные усилия, чтобы заметить такой вывод, будет нет необходимости в общем правиле, допускающем использование этих типов. Вопрос о том, когда признавать такой вывод, был оставлен в качестве вопроса качества реализации при условии, что компиляторы, которые предприняли какие-либо добросовестные усилия для удовлетворения потребностей своих клиентов, вероятно, справились бы лучше, чем если бы Стандарт пытался выписать набор точных правил.

Вместо того, чтобы пытаться найти способы, с помощью которых указатели типа элемента могут быть получены из объектов struct или union, g cc и clang вместо этого выбирают go сверх того, что на самом деле указано в гораздо меньшей степени, чем ожидали бы большинство членов комитета. Они будут обрабатывать операцию, выполненную непосредственно над lvalue, сформированным с использованием value.member или ptr->member, - это операция над родительским объектом. Они также распознают l-значения вида value.member[index] или ptr->member.index. С другой стороны, несмотря на то, что (array)[index] определено как эквивалентное (*((array)+(index))), они не будут распознавать (*((ptr->member)+(index))) как операцию над объектом, обозначенным ptr. Они также, как правило, без необходимости будут предполагать, что объекты структурного типа могут взаимодействовать с не связанными указателями на объекты типа члена.

Если кто-то пишет код, который выиграл бы от возможности выполнять штампование типов, моя рекомендация будет явно указать в документации, что для надежной работы требуется -fno-strict-aliasing. Цель правил наложения имен состояла в том, чтобы дать авторам компилятора свободу выполнять оптимизацию , которая не мешала бы тому, что их клиенты должны были сделать . Авторы компиляторов должны были распознавать и поддерживать потребности своих клиентов, независимо от того, требовал ли он от них этого.

0 голосов
/ 08 марта 2020

Штамповка союзного типа разрешена с C99 (несмотря на то, что только через сноску - но это часть красоты этого языка). С некоторыми ограничениями все в порядке.

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

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_257.htm

...