【问题标题】:C++ Equivalent to Designated Initializers?C++ 等价于指定初始化器?
【发布时间】:2010-10-25 18:06:27
【问题描述】:

最近我一直在研究一些嵌入式设备,我们有一些结构和联合需要在编译时初始化,以便我们可以将某些不需要修改的东西保存在闪存或 ROM 中,并且以性能成本节省一点闪存或 SRAM。目前该代码编译为有效的 C99,但如果没有此调整,它过去也可以编译为 C++ 代码,并且支持以这种方式编译的东西也很棒。防止这种情况发生的关键因素之一是我们使用了 C99 指定的初始化程序,这些初始化程序在 C++ 的 C 子集中不起作用。我不是 C++ 爱好者,所以我想知道在 C++ 兼容的 C 或 C++ 中可能有什么简单的方法可以使这种情况发生,仍然允许在编译时进行初始化,这样结构和联合就不需要了在 SRAM 中程序启动后初始化。

还有一点需要注意:指定初始化器使用的一个关键原因是初始化为不是联合的第一个成员。此外,为了保持与其他编译器的兼容性,坚持使用标准 C++ 或 ANSI C 是一个加分项(我知道 GNU 扩展提供了类似没有 C99 的指定初始化程序之类的东西)。

【问题讨论】:

  • 请注意,指定的初始化器现在可以在 g++ 中使用。我有 4.8.1 版本,我可以使用枚举中的初始化程序,它按预期工作。

标签: c++ c designated-initializer


【解决方案1】:

我不确定你是否可以在 C++ 中做到这一点。对于需要使用指定初始化程序初始化的内容,您可以将它们分别放在编译为 C99 的 .c 文件中,例如:

// In common header file
typedef union my_union
{
    int i;
    float f;
} my_union;

extern const my_union g_var;

// In file compiled as C99
const my_union g_var = { .f = 3.14159f };

// Now any file that #include's the header can access g_var, and it will be
// properly initialized at load time

【讨论】:

  • 我已经考虑过了,最终可能会走这条路。不幸的是,我们希望移植到一个使用 C++ 库来提供外围支持的平台。他们提供对在线编译器的访问,但他们将其限制为纯 C++ 模式(为简单起见)。我尝试获取预编译库并使用本地 GCC 工具链,但是在与 GCC 和 Newlib 链接时,有些符号无法从他们的库中解析(它们使用 Keil/ARM 的 RealView)。我可以在本地预编译所有 C99 代码并在线链接。我只是想让事情变得简单:-)
  • 这可行,但我相信如果您对工会/结构的成员使用非标准类型,这将很快变得混乱。
【解决方案2】:

基于成业的回答,加上 3 年的时间,C++11 现在可以保证编译时初始化:

union Bar
{
    constexpr Bar(int a) : a_(a) {}
    constexpr Bar(float b) : b_(b) {}
    int a_;
    float b_;
};

extern constexpr Bar bar1(1);
extern constexpr Bar bar2(1.234f);

组装:

    .globl  _bar1                   ## @bar1
    .p2align    2
_bar1:
    .long   1                       ## 0x1

    .globl  _bar2                   ## @bar2
    .p2align    2
_bar2:
    .long   1067316150              ## float 1.23399997

【讨论】:

    【解决方案3】:
    #ifdef __cplusplus
    struct Foo
    {
        Foo(int a, int b) : a(a), b(b) {}
        int a;
        int b;
    };
    
    union Bar
    {
        Bar(int a) : a(a) {}
        Bar(float b) : b(b) {}
        int a;
        float b;
    };
    
    static Foo foo(1,2);
    static Bar bar1(1);
    static Bar bar2(1.234f);
    #else 
     /* C99 stuff */
    #endif // __cplusplus
    

    在 C++ 中,联合也可以有构造函数。这可能是你想要的吗?

    【讨论】:

    • 初始化是在运行时还是编译时完成的?我想,这就是这里的关键问题。
    • 我必须参考 holly 标准才能确定,但​​我想,所有全局静态变量都已初始化并存储在可执行映像的数据部分中。最好的办法是用你的编译器试一试,看看它能做什么。
    • 不,空间将在可执行映像中分配,初始化为零并在运行时调用构造函数。不过,为了增加乐趣,嵌入式系统在最后一点上可能有点不一致 - 理论上,链接器会收集静态构造函数调用的列表,然后 libgcc 在进程引导期间调用它们,但取决于您的平台,它不能发生,或仅在您选择正确的构建选项时发生。
    【解决方案4】:

    这既是一个答案,也是一个问题。我意识到这个帖子已经死了,但这正是我今晚正在调查的内容。

    我做了一些探索,最接近我想要的东西(这与你想要的相似......我一直在使用图片并且不需要使用 c++,但我很好奇它是如何实现的可能会完成)是第一个代码示例:

    #include <iostream>
    
    using namespace std;
    
    extern "C" 
    {
        typedef struct stuff
        {
            int x;
            double y;
        } things;
    }
    
    int main()
    {
        things jmcd = { jmcd.x = 12, jmcd.y = 10.1234 };
        cout << jmcd.x << " " << jmcd.y << endl;
        return 0;
    }
    

    这与 C99 风格的指定初始化器外观非常相似,但需要注意的是我稍后会提到。 (如果您希望结构体由其中任何一个编译,您可能会将其包装在 #ifdef __cplusplus 中。)我查看的第二个版本的代码是这样的:

    #include <iostream>
    
    using namespace std;
    
    extern "C" 
    {
        typedef struct stuff
        {
            int x;
            double y;
        } things;
    }
    
    
    int main()
    {
        things jmcd;
        jmcd.x = 12;
        jmcd.y = 10.1234;
        cout << jmcd.x << " " << jmcd.y << endl;
        return 0;
    }
    

    基本上,从反汇编来看,第一个示例似乎实际上更慢。我查看了汇编输出,嗯,我一定有点生疏了。也许有人可以给我一些见解。第一个 cpp 的汇编输出编译后看起来像:

    main:
    .LFB957:
        .cfi_startproc
        .cfi_personality 0x0,__gxx_personality_v0
        pushl   %ebp
        .cfi_def_cfa_offset 8
        movl    %esp, %ebp
        .cfi_offset 5, -8
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    $0, 12(%esp)
        movl    $0, 16(%esp)
        movl    $0, 20(%esp)
        movl    $12, 12(%esp)
        movl    12(%esp), %eax
        movl    %eax, 12(%esp)
        fldl    .LC0
        fstpl   16(%esp)
        fldl    16(%esp)
        fstpl   16(%esp)
        movl    12(%esp), %eax
        movl    %eax, 4(%esp)
        fildl   4(%esp)
        fldl    16(%esp)
        faddp   %st, %st(1)
        fnstcw  2(%esp)
        movzwl  2(%esp), %eax
        movb    $12, %ah
        movw    %ax, (%esp)
        fldcw   (%esp)
        fistpl  4(%esp)
        fldcw   2(%esp)
        movl    4(%esp), %eax
        leave
        ret
        .cfi_endproc
    

    第二个例子看起来像:

    main:
    .LFB957:
        .cfi_startproc
        .cfi_personality 0x0,__gxx_personality_v0
        pushl   %ebp
        .cfi_def_cfa_offset 8
        movl    %esp, %ebp
        .cfi_offset 5, -8
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    $12, 12(%esp)
        fldl    .LC0
        fstpl   16(%esp)
        movl    12(%esp), %eax
        movl    %eax, 4(%esp)
        fildl   4(%esp)
        fldl    16(%esp)
        faddp   %st, %st(1)
        fnstcw  2(%esp)
        movzwl  2(%esp), %eax
        movb    $12, %ah
        movw    %ax, (%esp)
        fldcw   (%esp)
        fistpl  4(%esp)
        fldcw   2(%esp)
        movl    4(%esp), %eax
        leave
        ret
        .cfi_endproc
    

    这两个都是使用g++ -O0 -S main.cpp 命令生成的。显然,直观上效率较低的示例在指令数量方面生成了更有效的操作码。另一方面,在少数情况下,我可以想象这几条指令很关键。 (另一方面,我真的很难理解不是由人类编写的程序集,所以也许我遗漏了一些东西......)我认为这为詹姆斯提出的问题提供了一个解决方案,尽管很晚。接下来我应该测试的是 C99 中是否允许相同的初始化;如果可行,我认为它完全解决了詹姆斯的问题。

    免责声明:我不知道这是否适用于除 g++ 之外的任何其他编译器。

    【讨论】:

    • 对我有用 - 反正不在快速路径部分。比使用另一个 C 文件更好的解决方案!点赞!
    • 再次迟到,但是如果您在第一个解决方案中交换 jmcd.x 和 jmcd.y ,这将不起作用。这是因为它不是一个特殊的构造,它只是带有更多表达式的常规初始化。所以 jmcd.x = 12 被执行,然后这个表达式 (12) 的结果值被赋值给 struct (x) 的第一个字段。 y 也是一样。如果你交换它们,两个字段都是 12。
    • 对 C++ 中指定初始化程序的支持是 GNU 扩展,而不是 C++11 的一部分。即使使用 GCC 扩展,指定初始化也只允许用于 POD 类型。
    【解决方案5】:

    干孔报告:

    给定

    struct S {
      int mA;
      int mB;
      S() {}
      S(int b} : mB(b) {} // a ctor that does partial initialization
    };
    

    我尝试从 S 派生 S1,其中 S1 的内联默认构造函数调用 S(int) 并传递一个硬编码值...

    struct S1 {
      S1() : S(22) {}
    } s1;
    

    ... 然后用 gcc 4.0.1 -O2 -S 编译。希望优化器会看到 s1.mB 必然是 22 并在编译时为其赋值,但来自汇编器...

        movl    $22, 4+_s1-"L00000000002$pb"(%ebx)
    

    ...看起来生成的代码在 main 之前在运行时进行了初始化。即使它成功了,它也很难编译为 C99,并且会为你想要初始化的每个对象派生一个类。所以,不要打扰。

    【讨论】:

    • 谢谢。它不一定必须编译为 C99。初始化器在很多地方,但它们是使用一些宏定义的,所以我可以通过最小的修改 ifdef __cplusplus,这也很好。
    【解决方案6】:

    以下代码使用 g++ 编译没有问题:

    #include <iostream>
    
    struct foo
    {
      int a;
      int b;
      int c;
    };
    
    union bar
    {
      int a;
      float b;
      long c;
    };
    
    static foo s_foo1 = {1,2,3};
    static foo s_foo2 = {1,2};
    static bar s_bar1 = {42L};
    static bar s_bar2 = {1078523331}; // 3.14 in float
    
    
    int main(int, char**)
    {
      std::cout << s_foo1.a << ", " <<
                   s_foo1.b << ", " <<
                   s_foo1.c << std::endl;
    
      std::cout << s_foo2.a << ", " <<
                   s_foo2.b << ", " <<
                   s_foo2.c << std::endl;
    
      std::cout << s_bar1.a << ", " <<
                   s_bar1.b << ", " <<
                   s_bar1.c << std::endl;
    
      std::cout << s_bar2.a << ", " <<
                   s_bar2.b << ", " <<
                   s_bar2.c << std::endl;
    
      return 0;
    }
    

    结果如下:

    $ g++ -o ./test ./test.cpp
    $ ./test
    1, 2, 3
    1, 2, 0
    42, 5.88545e-44, 42
    1078523331, 3.14, 1078523331
    

    C++ 初始化器唯一的事情是您需要初始化结构的所有元素,否则其余元素将被初始化为零。你不能挑挑拣拣。但这对于您的用例来说应该仍然可以。

    还有一点需要注意:指定初始化器使用的一个关键原因是初始化为不是联合的第一个成员。

    为此,您需要使用示例中显示的“解决方法”,我通过提供等效的 int 值来设置“float”成员。这有点小技巧,但如果它可以解决您的问题。

    【讨论】:

    • 这只是因为 42L 被隐式转换为整数。如果你想将 float 成员初始化为 3.5,你不能在 C++ 中这样做。
    • 您不能初始化多个联合成员。毕竟它是一个联合;-) 但是,如果你想初始化“浮点”部分,你需要用等效的整数(可能是十六进制数)初始化它
    • 是的——但是 C99 的指定初始化器允许您初始化联合体的一个元素而不是第一个元素,而无需求助于诸如找出浮点实现定义的等效整数等技巧。
    • 我不是要求初始化多个成员,只是不是一直初始化第一个。它比使用等价物做事更复杂的原因是其中一些是工会的更复杂的成员。我可以指出我们的源代码(修改 Lua 以在小型设备上运行),但要了解它的工作原理需要一点挖掘。如果有兴趣,这里是一个起点:svn.berlios.de/svnroot/repos/elua/trunk/src/lua/lrotable.h 描述(是的,它显示为原始,文档还没有在其他地方):svn.berlios.de/svnroot/repos/elua/trunk/doc/en/arch_ltr.html
    • 好吧,如果您想将其编译为标准 C++,恐怕您需要使用“等价物”之类的技巧。而且您只需要它们用于进入 ROM 的数据,对于进入 RAM 的所有其他数据,您可以像其他人指出的那样创建常规构造函数。
    猜你喜欢
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 2020-03-11
    • 1970-01-01
    • 2014-07-27
    • 1970-01-01
    • 2020-08-10
    相关资源
    最近更新 更多