C++ 连续内存布局中的匿名联合

Anonymous Unions in C++ Contiguous Memory Layout

提问人:The Floating Brain 提问时间:10/9/2023 最后编辑:SebastianThe Floating Brain 更新时间:11/9/2023 访问量:134

问:

struct Vec0
{
    union { float x, r, h; };
    union { float y, g, s; };
    union { float z, b, v; };
};

我想问一下,C++ 标准是否保证此数据将与我编写的数据完全相同

struct Vec1
{
    float x, y, z;
};

地址紧跟在地址加上 + 填充位/对齐的长度之后(我认为)。yxfloat

认为它确实(保证布局),但很好奇它是否由 ISO C++ 标准保证

更新:

澄清一下,我对这个问题很感兴趣,ISO C++ 标准中是否有任何保证

offsetof(Vec0, x /*or r h*/) == offsetof(Vec1, x)
offsetof(Vec0, y /*or g s*/) == offsetof(Vec1, y)
offsetof(Vec0, z /*or b v*/) == offsetof(Vec1, z)

这可能属于布局兼容的类别

我还对 的偏移量是否感兴趣(但不一定是因为人们可能无法在 //etc 之外使用,也许除非在假设中它能够访问 //etc 之外的数据成员)如果它们的所有数据成员都成为 /,则每个数据成员在两者之间将是相同的。offsetofoffsetofstructclassprivateprotectedstructclassprivateprotected

另外,请注意,这不是软件设计/架构/最佳实践问题,我知道在大多数情况下有更好的方法来访问这些数据成员。

C++ 匿名 内存布局 标准布局

评论

0赞 Pepijn Kramer 10/9/2023
注意 C++ 有 ,当你详细查看它们时,C++ 中的并集并不是那么好(很多未定义的行为)。此外,通常不应编写依赖于实际内存布局的代码。如果你这样做,你可能仍然需要使用编译器的提示(align_as、包等......std::variant
4赞 HolyBlackCat 10/9/2023
保证是相同的,但这两个代码段都不能保证成员之间不会有填充。(尽管在实践中,这两种情况都不会出现。添加 .)static_assert
0赞 HolyBlackCat 10/9/2023
@PepijnKramer OP 正在使用工会来为成员提供替代名称。 对此不起作用。variant
1赞 Some programmer dude 10/9/2023
在 C++ 联合中,只能从写入的最后一个成员读取。所以如果写到 ,只能从中读取。您无法从 或 读取 。xxrh
1赞 BoP 10/9/2023
“即使他们有衬垫,也应该是统一的,对吧”该标准对此没有任何说明,因此无法保证。结构体允许有填充 - 仅此而已。没有“合理”条款。

答:

4赞 user17732522 10/9/2023 #1

认为确实如此,但很好奇它是否受到 ISO C++ 标准的保证?

不是,在 or 成员之间可以有任何任意填充,这两个示例之间不必一致。这适用于 ISO 标准 C++ 和 ISO 标准 C。unionfloat

但是,如果没有充分的理由添加此类填充,则添加它没有任何意义。类布局是 ABI 的一部分,因此在编译所假定的 C++ ABI 或基础 C ABI 的规范中指定。您可以在相应的 ABI 规范中获得特定编译器/平台组合的明确答案。


问题编辑后:

offsetof(Vec0, x /*or r h*/) == offsetof(Vec1, x)

保证所有选项,因为 和 在您的示例中是标准布局。因此,所有这些偏移量都必须完全是 。Vec0Vec10

另一方面

offsetof(Vec0, y /*or g s*/) == offsetof(Vec1, y)
offsetof(Vec0, z /*or b v*/) == offsetof(Vec1, z)

在任何情况下,C++ 标准都不能保证。第一个(和后续)成员之后的填充在两个类之间可能不同,尽管我看不出 ABI 在您的特定示例中做出该选择的任何充分理由。

这可能属于布局兼容的类别

Vec0并且布局兼容。它已经无法满足链接中列出的要求,因为单个非类成员(如)永远不能与(联合)类成员(如第一个匿名联合)在布局上兼容。Vec1Vec1::xVec0

请注意,类型与布局不兼容并不意味着其成员不会具有相同的布局。它只是指定了一个他们实际上必须这样做的特定条件。布局兼容性条件仅与类的一个非常具体的用法相关,即当两者都显示为联合的成员时,在这种情况下,该联合的活动成员规则存在例外。这是布局兼容性与语言相关的唯一情况。

我还对天气的子情况感兴趣,或者不是 offsetof(但不一定是 offsetof,因为人们可能无法在 struct/class/etc 之外使用 offsetof,也许除了假设它能够访问 struct/class/etc 之外的私有/受保护数据成员)如果所有数据成员都设为私有/受保护,则两者之间的每个数据成员都是相同的。

如果不是所有成员都具有相同的可访问性 (//),则该类不再是标准布局类,甚至第一个成员位于偏移量零的保证也不再成立。privateprotectedpublic

评论

0赞 The Floating Brain 10/10/2023
感谢您的回答,但是,我确实更新了问题以考虑长度 + 填充 + 对齐方式。我问第一个和第二个示例之间的对齐方式是否相同。例如,如果第一个 x 和第二个 x 的偏移量相同(考虑填充 + 对齐)。
0赞 user17732522 10/10/2023
@TheFloatingBrain 我不确定你说的“考虑填充+对齐”是什么意思?两者之间的填充可以不同。如果你减去它,我不确定你还关心什么。虽然,如果您想获得更多异国情调,则标准也无法保证具有相同的尺寸。floatunion { float x, r, h; }
2赞 user17732522 11/8/2023
@TheFloatingBrain 据我所知,这个问题只有两个正确答案。特别是,工会的规模是最大成员的规模是错误的。相反,它至少与最大成员的大小一样大。在末尾不添加填充的决定也不一定只是为了对齐,尽管除了确保所有成员的对齐之外,可能没有充分的理由添加更多内容。此外,在 C++ 中有一个活动成员的概念,那里的大多数答案都没有承认。
2赞 user17732522 11/8/2023
@TheFloatingBrain “如果 Vec 的对齐方式为 A,则 x 之后的填充宽度为 P,那么 Vec 的第二个版本在 x/r/h 之后是否具有相同的对齐方式和填充”:C++(或 C)标准不能保证这一点。它应该在正在使用的 ABI 中指定,我看不出 ABI 有任何充分的理由在此特定实例中指定不同的对齐方式或填充方式。这就是我最初想说的答案。
1赞 user17732522 11/8/2023
@TheFloatingBrain我已经更新了您更新问题的答案。
2赞 Ben Voigt 11/8/2023 #2

请注意,虽然另一个答案正确地指出您的尝试失败,但有一个替代表达式公式在内部和Vec1

union Vec2
{
    struct { float x, y, z; } location;
    struct { float r, g, b; } color;
    struct { float h, s, v; } light;
};

许多编译器允许您省略名称 、 作为扩展名,但这不符合标准。locationcolorlight

评论

0赞 The Floating Brain 11/9/2023
这是一个很好的见解!感谢您的额外回答。我不确定它是否不符合标准: 发件人:en.cppreference.com/w/cpp/language/union “匿名工会的成员被注入封闭范围(并且不得与在那里声明的其他名称冲突)。”另外:stackoverflow.com/questions/47694657/......
2赞 Ben Voigt 11/9/2023
@TheFloatingBrain:这个答案考虑了联合内部的匿名结构,与结构内部的匿名联合不同。
0赞 The Floating Brain 11/21/2023
我的错误,你是对的!