首页 > 解决方案 > 使用嵌套结构初始化联合

问题描述

我正在将 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);

    Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD};


    Word_t w2 = {0};
    w2._word16Bits = {0x1234, 0xABCD};

    return 0;
}

哪个有效,但它不允许我明确地说.mostSignificant16Bits = 0x1234——我认为这很有用,特别是在阅读代码时。

我尝试了一些方法,例如定义静态成员、创建用户定义的构造函数,但仍然不知道如何简化我将要做的重构。理想情况下,我希望变量声明保持原样,Word_t w2 = (Word_t) {.mostSignificant16Bits = 0x1234, .leastSignificant16Bits = 0xABCD}而所有更改都在Word_t.

标签: c++cunionsporting

解决方案


句法问题

聚合初始化中的指定初始化程序是C++20 标准的正式一部分。

然而,与 C99 相比,它们具有严重的限制:

  • 它们必须以与声明相同的顺序出现;
  • 所有指定的元素必须是聚合的直接成员;
  • 仅当初始化器嵌套时才可能嵌套

在您的情况下,以下内容可以编译,但无法提供您期望从显式命名中获得的灵活性优势:

Word_t w2 = (Word_t) {._word16Bits  { .leastSignificant16Bits = 0xABCD, .mostSignificant16Bits = 0x1234} };

更严重的问题

首先,如果它可以工作,这段代码是不可移植的:它假设目标架构的字节序很小。

其次,这在这里很重要,C++ 对联合有很强的限制。鉴于对象生命周期的一致性,这些都是必要的。尤其:

[class.union]/1:在一个union中,任何时候最多可以有一个非静态数据成员处于活动状态,即最多可以将一个非静态数据成员的值存储在一个随时联合。

因此,如果您在一个成员处于活动状态(初始化程序中使用的成员)的情况下构造联合,则另一个成员处于非活动状态,您不应访问它。唯一可预见的例外情况不适用于您的情况:

[注意:为了简化联合的使用,我们做了一个特殊的保证:如果一个标准布局联合包含几个共享一个公共初始序列的标准布局结构,并且如果这个标准布局联合类型的对象包含以下之一标准布局结构,允许检查任何标准布局结构成员的公共初始序列;——尾注]

该标准还提供了有关更改工会积极成员的方式的提示:

[注意:通常,必须使用显式析构函数调用和放置新运算符来更改联合的活动成员。——尾注]

这对于简单的标量类型来说,它可以在大多数主流编译器上编译和工作,就像你期望的那样。

但关键是,您对 unions 的使用与标准不兼容。它是 UB,即使它现在可以在某些实现上运行,但在每个新的编译器版本中,您都无法保证它会继续工作,从而使您的所有投资都面临风险。

为什么是时候改变方法了?

与 C++ 一样,C99 对联合的限制较少。但它也没有提供关于在设置另一个工会成员时可能在一个工会成员中读取的值的确切保证:

6.2.6.1/7:当值存储在联合类型对象的成员中时,对象表示中不对应于该成员但对应于其他成员的字节采用未指定的值。

附件 C99/J 中提到,这种联合使用是一种未指定的行为,可能会产生可移植性问题。


推荐阅读