使用嵌套结构初始化联合
Initialize union with nested structs
我正在将 C99 代码移植到 C++(14 或 17),并且在许多地方使用了列表初始化器。现在我遇到编译错误,想知道初始化由结构嵌套的联合的最简单方法。例如,下面的 C 代码片段工作得很好:
#include <stdint.h>
typedef union Word_t
{
uint32_t word32Bits;
struct
{
uint16_t leastSignificant16Bits;
uint16_t mostSignificant16Bits;
};
} Word_t;
int main()
{
Word_t w1 = (Word_t) {.word32Bits = 0x1234ABCD};
printf("%x\n", w1.word32Bits);
Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
printf("%x\n", w2.word32Bits);
return 0;
}
$ gcc test.c --std=c99 -o a && ./a
1234abcd
1234abcd
但是,在 C++ 中它无法编译:
#include <stdint.h>
typedef union Word_t
{
uint32_t word32Bits;
struct
{
uint16_t leastSignificant16Bits;
uint16_t mostSignificant16Bits;
} _word16Bits;
} Word_t;
int main()
{
Word_t w1 = (Word_t) {.word32Bits = 0x1234ABCD};
printf("%x\n", w1.word32Bits);
Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
printf("%x\n", w2.word32Bits);
return 0;
}
```bash
$ g++ test.c --std=c++14 -o a && ./a
test.c: In function ‘int main()’:
test.c:57:92: error: ‘Word_t’ has no non-static data member named ‘mostSignificant16Bits’
Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
我找到的解决方案是零初始化,然后按如下方式设置结构的内部值:
int main()
{
Word_t w1 = (Word_t) {.word32Bits = 0x1234ABCD};
printf("%x\n", w1.word32Bits);
<del>Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
</del>
Word_t w2 = {0};
w2._word16Bits = {0x1234, 0xABCD};
return 0;
}
有效,但它不允许我明确地说 .mostSignificant16Bits = 0x1234
例如——我认为这很有用,特别是在阅读代码时。
我尝试了一些事情,比如定义静态成员、创建用户定义的构造函数,但仍然不知道如何简化我将要做的重构。理想情况下,我想将变量声明保持原样 Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD}
,而所有更改都在 Word_t
.
的定义中完成
这适用于 C 和 C++:
#include <stdio.h>
#include <stdint.h>
typedef union Word_t
{
uint32_t word32Bits;
struct
{
uint16_t leastSignificant16Bits;
uint16_t mostSignificant16Bits;
} _word16Bits;
} Word_t;
int main()
{
Word_t w1 = {.word32Bits = 0x1234ABCD};
printf("%x\n", w1.word32Bits);
Word_t w2 = {._word16Bits={.leastSignificant16Bits = 0xABCD, .mostSignificant16Bits = 0x1234}};
printf("%x\n", w2.word32Bits);
return 0;
}
编辑:指定初始值设定项需要 --std=c++2a
而不是 --std=c++14
。
$ g++ -Wall -Wextra test.c --std=c++2a -pedantic -pedantic-errors -o a && ./a
1234abcd
1234abcd
$ gcc -Wall -Wextra test.c -pedantic -pedantic-errors -o a && ./a
1234abcd
1234abcd
注意:在 C++ 中,您必须按照与结构声明中相同的顺序指定标记的初始值设定项。
如 Ted Lyngmo 的评论中所述,写入一个联合成员并从另一个成员读取是 C++ 中的未定义行为。
在任何情况下,使用联合从较长的数据类型中提取较短的部分或将它们组合起来都是依赖于实现的。在大端系统上,leastSignificant16Bits
和 mostSignificant16Bits
的顺序会颠倒。
语法问题
Designated initializers in aggregate initialization are formally part of the C++20 standard。
然而,与 C99 相比,它们具有严格的限制:
- 它们必须以与声明相同的顺序出现;
- 所有指定元素必须是聚合的直接成员;
- 只有嵌套初始化程序才能嵌套
在您的情况下,可以编译以下内容,但无法提供您期望从显式命名中获得的灵活性优势:
Word_t w2 = (Word_t) {._word16Bits { .leastSignificant16Bits = 0xABCD, .mostSignificant16Bits = 0x1234} };
更严重的问题
首先,此代码(如果可以的话)不可移植:它采用 little endianness 目标体系结构。
其次,这在这里很重要,C++ 对联合有很强的约束。鉴于对象生命周期的一致性,这些都是必要的。特别是:
[class.union]/1: In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one
of the non-static data members can be stored in a union at any time.
因此,如果您构建的联合中有一个成员处于活动状态(在您的初始化程序中使用的成员),则另一个成员不活动,您不应该访问它。唯一预见的例外不适用于您的情况:
[ Note: One special guarantee is made in order to simplify the use of
unions: If a standard-layout union contains several standard-layout
structs that share a common initial sequence, and if an object
of this standard-layout union type contains one of the standard-layout
structs, it is permitted to inspect the common initial sequence of any
of standard-layout struct members; — end note ]
标准还提示了改变工会活跃成员的方式:
[ Note: In general, one must use explicit destructor calls and
placement new operators to change the active member of a union. — end
note ]
这是针对简单标量类型的说法,它可能会在大多数主流编译器上按您预期的方式编译和工作。
但重点是,您对联合的使用与标准不兼容。它是 UB,即使它现在可以在某些实现上运行,但在每个新的编译器版本中,您都无法保证它会继续运行,从而使您的所有投资都面临风险。
为什么是时候改变方法了?
与 C++ 相比,C99 对联合的限制更少。但是当另一个联合成员被设置时,它也没有给出关于一个联合成员可能读取的值的坚定保证:
6.2.6.1/7: When a value is stored in a member of an object of union type, the bytes of the object representation that do not
correspond to that member but do correspond to other members take
unspecified values.
附件 C99/J 中提到联合的这种使用是一种未指定的行为,可能会产生可移植性问题。
我正在将 C99 代码移植到 C++(14 或 17),并且在许多地方使用了列表初始化器。现在我遇到编译错误,想知道初始化由结构嵌套的联合的最简单方法。例如,下面的 C 代码片段工作得很好:
#include <stdint.h>
typedef union Word_t
{
uint32_t word32Bits;
struct
{
uint16_t leastSignificant16Bits;
uint16_t mostSignificant16Bits;
};
} Word_t;
int main()
{
Word_t w1 = (Word_t) {.word32Bits = 0x1234ABCD};
printf("%x\n", w1.word32Bits);
Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
printf("%x\n", w2.word32Bits);
return 0;
}
$ gcc test.c --std=c99 -o a && ./a
1234abcd
1234abcd
但是,在 C++ 中它无法编译:
#include <stdint.h>
typedef union Word_t
{
uint32_t word32Bits;
struct
{
uint16_t leastSignificant16Bits;
uint16_t mostSignificant16Bits;
} _word16Bits;
} Word_t;
int main()
{
Word_t w1 = (Word_t) {.word32Bits = 0x1234ABCD};
printf("%x\n", w1.word32Bits);
Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
printf("%x\n", w2.word32Bits);
return 0;
}
```bash
$ g++ test.c --std=c++14 -o a && ./a
test.c: In function ‘int main()’:
test.c:57:92: error: ‘Word_t’ has no non-static data member named ‘mostSignificant16Bits’
Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
我找到的解决方案是零初始化,然后按如下方式设置结构的内部值:
int main()
{
Word_t w1 = (Word_t) {.word32Bits = 0x1234ABCD};
printf("%x\n", w1.word32Bits);
<del>Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};
</del>
Word_t w2 = {0};
w2._word16Bits = {0x1234, 0xABCD};
return 0;
}
有效,但它不允许我明确地说 .mostSignificant16Bits = 0x1234
例如——我认为这很有用,特别是在阅读代码时。
我尝试了一些事情,比如定义静态成员、创建用户定义的构造函数,但仍然不知道如何简化我将要做的重构。理想情况下,我想将变量声明保持原样 Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD}
,而所有更改都在 Word_t
.
这适用于 C 和 C++:
#include <stdio.h>
#include <stdint.h>
typedef union Word_t
{
uint32_t word32Bits;
struct
{
uint16_t leastSignificant16Bits;
uint16_t mostSignificant16Bits;
} _word16Bits;
} Word_t;
int main()
{
Word_t w1 = {.word32Bits = 0x1234ABCD};
printf("%x\n", w1.word32Bits);
Word_t w2 = {._word16Bits={.leastSignificant16Bits = 0xABCD, .mostSignificant16Bits = 0x1234}};
printf("%x\n", w2.word32Bits);
return 0;
}
编辑:指定初始值设定项需要 --std=c++2a
而不是 --std=c++14
。
$ g++ -Wall -Wextra test.c --std=c++2a -pedantic -pedantic-errors -o a && ./a
1234abcd
1234abcd
$ gcc -Wall -Wextra test.c -pedantic -pedantic-errors -o a && ./a
1234abcd
1234abcd
注意:在 C++ 中,您必须按照与结构声明中相同的顺序指定标记的初始值设定项。
如 Ted Lyngmo 的评论中所述,写入一个联合成员并从另一个成员读取是 C++ 中的未定义行为。
在任何情况下,使用联合从较长的数据类型中提取较短的部分或将它们组合起来都是依赖于实现的。在大端系统上,leastSignificant16Bits
和 mostSignificant16Bits
的顺序会颠倒。
语法问题
Designated initializers in aggregate initialization are formally part of the C++20 standard。
然而,与 C99 相比,它们具有严格的限制:
- 它们必须以与声明相同的顺序出现;
- 所有指定元素必须是聚合的直接成员;
- 只有嵌套初始化程序才能嵌套
在您的情况下,可以编译以下内容,但无法提供您期望从显式命名中获得的灵活性优势:
Word_t w2 = (Word_t) {._word16Bits { .leastSignificant16Bits = 0xABCD, .mostSignificant16Bits = 0x1234} };
更严重的问题
首先,此代码(如果可以的话)不可移植:它采用 little endianness 目标体系结构。
其次,这在这里很重要,C++ 对联合有很强的约束。鉴于对象生命周期的一致性,这些都是必要的。特别是:
[class.union]/1: In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.
因此,如果您构建的联合中有一个成员处于活动状态(在您的初始化程序中使用的成员),则另一个成员不活动,您不应该访问它。唯一预见的例外不适用于您的情况:
[ Note: One special guarantee is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout structs that share a common initial sequence, and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members; — end note ]
标准还提示了改变工会活跃成员的方式:
[ Note: In general, one must use explicit destructor calls and placement new operators to change the active member of a union. — end note ]
这是针对简单标量类型的说法,它可能会在大多数主流编译器上按您预期的方式编译和工作。
但重点是,您对联合的使用与标准不兼容。它是 UB,即使它现在可以在某些实现上运行,但在每个新的编译器版本中,您都无法保证它会继续运行,从而使您的所有投资都面临风险。
为什么是时候改变方法了?
与 C++ 相比,C99 对联合的限制更少。但是当另一个联合成员被设置时,它也没有给出关于一个联合成员可能读取的值的坚定保证:
6.2.6.1/7: When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values.
附件 C99/J 中提到联合的这种使用是一种未指定的行为,可能会产生可移植性问题。