使用联合体的C++未定义行为

8

我刚刚在阅读有关匿名结构的文章,发现它并不是标准的用法,一些常见的用例也存在未定义行为的问题...

下面是基本用例:

struct Point {
    union {
       struct {
           float x, y;
       };
       float v[2];
    };
};

因此,写入x,然后从v [0]读取,在这种情况下是未定义的,因为您期望它们相同,但可能不是这样。

不确定是否在标准中定义了相同类型的联合体...

union{ float a; float b; };

在写入a并从b读取时是否未定义?

也就是说,标准是否有关于数组和连续变量的二进制表示的规定。


你可以选择代码片段,然后按Ctrl+K。只有在短的单行代码中才使用反引号。 - jrok
联合体中的未命名结构体不是 ISO-C++ 的一部分(尽管许多编译器支持它们作为扩展)。 - ComicSansMS
@ComicSansMS 很好的观点,但是...给这个struct一个名字,他的问题仍然是一样有价值的。 - James Kanze
@JamesKanze 当然,我并不是想批评这个问题,我只是想指出它。不过现在我想了想,当被强制使用命名结构体时,OP使用的特定结构变得不那么有用了。 - ComicSansMS
3个回答

7
这个标准规定对于联合体(unions)内的任意一个元素进行读取,如果这个元素不是最后一个写入的,其结果将是未定义的行为。理论上来说,编译器可以生成某种代码以跟踪读取和写入,并在违反规则时触发信号(即使两个元素的类型相同)。编译器还可以利用这个事实进行某种优化: 如果您写入了 a (或 x),它会假定您不会读取b(或 v[0]) 在优化时。

实际上,据我所知,所有的编译器都支持此功能,但需要明确可见联合体的情况。在很多(大多数?全部?)情况下,即使符合规则,如果联合体不可见,使用也将失败(例如:)

union  U { int i; float f; };

int f( int* pi, int* pf ) { int r = *pi; *pf = 3.14159; return r; }

//  ...
U u;
u.i = 1;
std::cout << f( &u.i, &u.f );

实际上,我已经看到过使用g++编译器失败的情况,尽管根据标准来说,这是完全合法的。

此外,即使编译器支持写入 Point::x 并从 Point::v[0] 读取,也不能保证 Point::yPoint::v[1] 具有相同的物理地址。


谢谢回复!你提到 yv[1] 没有保证的最后一点让我想起了一些 OpenGL 代码,特别是将顶点数据传递给 GPU。不确定你是否熟悉 glVertexAttribPointer(),但我认为代码看起来像这样:struct Vertex { float x, y, z; float u, v; } vertices[10]; glVertexAttribPointer( ..., 3, GL_FLOAT, ... , &verticies.x);我认为 OpenGL 会把它视为一个数组,但如果没有保证是相同的,那么这段代码将被视为未定义的? - johndoe
如果他们实际上将"&verticies.x"视为数组第一个元素的指针,则该行为是未定义的(并且至少有过编译器在这种情况下会在执行时崩溃)。 - James Kanze
(我希望这个答案不会太晚!)你在 f() 的第二个参数中打算使用 float* 而不是 int* 吗?它是如何在 g++ 中失败的? - janm
为什么这是合法的?你正在将float *传递给int *,这不应该编译。如果你将它更正为float *,那是否会被严格的指针别名禁止呢? - Neil Kirk

0

标准要求在联合中,“每个数据成员都被分配,就好像它是结构体的唯一成员一样。”(9.5)

它还要求struct { float x, y; }float v[2]必须具有相同的内部表示(9.2),因此您可以安全地将一个重新解释为另一个

综合这两条规则,保证了您描述的union将在写入内存时正常工作。然而,由于标准仅要求最后一个写入的数据成员有效,理论上可能会出现实现失败的情况,如果联合只用作局部变量。不过,我很惊讶如果这种情况真的发生了。


1
这里唯一的“保证”是,如果您尝试读取最后一个写入之外的成员,则没有保证。我认为内部表示要求的讨论并不涉及其中。当然,GCC和其他主要编译器将愉快地提供“预期”的类型转换行为,但我不会向任何人暗示他们可以依赖此行为。 - underscore_d

-7

我不明白为什么你使用了float v[2];

一个点结构的简单联合可以定义为:

union{

struct {

    float a;
    float b;
};

} Point;

您可以通过以下方式访问union中的值:

Point.a = 10.5; 

point.b = 12.2; //example

2
联合的整个意义在于你可以同时通过 Point.xPoint.v[0] 访问 x。例如,在处理喜欢数组或基于坐标的形式的不同种类的API时,这非常有用。 - ComicSansMS
1
这不是问题所在,而且你的union甚至没有意义可以被视为union,一个struct会做得更好(但再次强调,这对于实际问题完全无关)。 - Christian Rau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接