37

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

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

4

6 回答 6

23

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

// 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
于 2009-05-13T04:10:23.687 回答
18

基于 Shing Yip 的回答,并利用 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
于 2012-07-21T13:43:21.933 回答
2
#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++ 中,联合也可以有构造函数。可能这就是你想要的吗?

于 2009-05-13T05:20:11.670 回答
2

这既是一个答案,也是一个问题。我意识到这个线程已经死了,但这正是我今晚正在研究的。

我做了一些探索,最接近我想要的东西(这与你想要的相似......我一直在使用图片并且不需要使用 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++ 以外的任何其他编译器是否有效或行为相似。

于 2011-07-12T05:45:53.443 回答
0

以下代码使用 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”成员。这有点像黑客,但如果它解决了你的问题。

于 2009-05-13T04:25:13.303 回答
0

干孔报告:

给定

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,并且会为你想要初始化的每个对象派生一个类。所以,不要打扰。

于 2009-05-13T04:28:28.930 回答