C 联合和未定义行为
C unions and undefined behaviour
在下面的示例代码中,是否有任何未定义或实现定义的行为?我可以为联合体的一个成员赋值并从另一个成员读取它吗?
#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
它正在反向打印 words/bytes 因为 x86 是小端,正如预期的那样。
C99 允许联合类型双关(尽管只是通过脚注 - 但它是这种语言的美的一部分)。有一些限制是可以的。
If the member used to read the contents of a union object is not the
same as the member last used tostore a value in the object, the
appropriate part of the object representation of the value is
reinterpretedas an object representation in the new type as
described in 6.2.6 (a process sometimes called
‘‘typepunning’’). This might be a trap representation.
我认为该标准的作者从未就应该要求或期望哪些结构在什么情况下发挥作用达成共识。相反,他们将问题视为 "quality of implementation" 问题,依靠实施来支持客户可能需要的任何结构。
C 标准指定通过除最后写入的成员以外的成员读取联合对象将使用新类型重新解释其中的字节。但是,如果查看可用于访问结构或联合对象的左值类型列表,就会发现没有规定可以使用非字符成员类型的对象来访问结构或联合。在使用成员类型的指针或左值的大多数情况下,它显然是从指向父类型的指针或父类型的左值派生出来的,如果编译器做出任何合理的努力来注意到这种派生,就会有不需要允许使用这些类型的一般规则。何时承认这种推导的问题被留作实施质量问题,假设编译器真正努力满足其客户的需求可能会比标准试图写出更好的工作一套精确的规则。
然而,gcc 和 clang 并没有努力寻找可以从结构或联合对象派生成员类型指针的方法,而是选择超出实际指定的范围,其程度远低于大多数委员会成员们会预料到的。他们会将直接对使用 value.member
或 ptr->member
形成的左值执行的操作视为对父对象的操作。他们还将识别 value.member[index]
或 ptr->member.index
形式的左值。另一方面,尽管 (array)[index]
被 定义为 等同于 (*((array)+(index)))
,但他们不会将 (*((ptr->member)+(index)))
识别为对ptr
标识的对象。他们通常还会不必要地假设结构类型的对象可能与指向成员类型对象的不相关指针交互。
如果编写的代码会受益于执行类型双关的能力,我的建议是在文档中明确说明可靠操作需要 -fno-strict-aliasing
。别名规则的目的是让编译器编写者可以自由地执行优化 ,而不会干扰他们的客户需要做的事情 。编译器作者应该承认并支持他们客户的需求,而不管标准是否要求他们这样做。
is there any undefined or implementation defined behavior?
一些基本问题:
struct { float X, Y, Z; };
可能在 X, Y, Z
之间填充 printf("X: %f; Y: %f; Z: %f\n", p.X, p.Y, p.Z);
未定义的行为,因为 p.Z
等可能未初始化。
i.Long = 0xDEADBEEF; printf("0x%4x%4x\n", i.Words[0], i.Words[1]);
导致实现定义的行为,因为 C 不需要特定的 endian。 (看来 OP 已经知道了。)
Can I assign a value to one member of a union and read it back from another?
是 - 在限制范围内。其他答案很好地解决了这部分问题。
在下面的示例代码中,是否有任何未定义或实现定义的行为?我可以为联合体的一个成员赋值并从另一个成员读取它吗?
#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
它正在反向打印 words/bytes 因为 x86 是小端,正如预期的那样。
C99 允许联合类型双关(尽管只是通过脚注 - 但它是这种语言的美的一部分)。有一些限制是可以的。
If the member used to read the contents of a union object is not the same as the member last used tostore a value in the object, the appropriate part of the object representation of the value is reinterpretedas an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘typepunning’’). This might be a trap representation.
我认为该标准的作者从未就应该要求或期望哪些结构在什么情况下发挥作用达成共识。相反,他们将问题视为 "quality of implementation" 问题,依靠实施来支持客户可能需要的任何结构。
C 标准指定通过除最后写入的成员以外的成员读取联合对象将使用新类型重新解释其中的字节。但是,如果查看可用于访问结构或联合对象的左值类型列表,就会发现没有规定可以使用非字符成员类型的对象来访问结构或联合。在使用成员类型的指针或左值的大多数情况下,它显然是从指向父类型的指针或父类型的左值派生出来的,如果编译器做出任何合理的努力来注意到这种派生,就会有不需要允许使用这些类型的一般规则。何时承认这种推导的问题被留作实施质量问题,假设编译器真正努力满足其客户的需求可能会比标准试图写出更好的工作一套精确的规则。
然而,gcc 和 clang 并没有努力寻找可以从结构或联合对象派生成员类型指针的方法,而是选择超出实际指定的范围,其程度远低于大多数委员会成员们会预料到的。他们会将直接对使用 value.member
或 ptr->member
形成的左值执行的操作视为对父对象的操作。他们还将识别 value.member[index]
或 ptr->member.index
形式的左值。另一方面,尽管 (array)[index]
被 定义为 等同于 (*((array)+(index)))
,但他们不会将 (*((ptr->member)+(index)))
识别为对ptr
标识的对象。他们通常还会不必要地假设结构类型的对象可能与指向成员类型对象的不相关指针交互。
如果编写的代码会受益于执行类型双关的能力,我的建议是在文档中明确说明可靠操作需要 -fno-strict-aliasing
。别名规则的目的是让编译器编写者可以自由地执行优化 ,而不会干扰他们的客户需要做的事情 。编译器作者应该承认并支持他们客户的需求,而不管标准是否要求他们这样做。
is there any undefined or implementation defined behavior?
一些基本问题:
struct { float X, Y, Z; };
可能在 X, Y, Z
之间填充 printf("X: %f; Y: %f; Z: %f\n", p.X, p.Y, p.Z);
未定义的行为,因为 p.Z
等可能未初始化。
i.Long = 0xDEADBEEF; printf("0x%4x%4x\n", i.Words[0], i.Words[1]);
导致实现定义的行为,因为 C 不需要特定的 endian。 (看来 OP 已经知道了。)
Can I assign a value to one member of a union and read it back from another?
是 - 在限制范围内。其他答案很好地解决了这部分问题。