Question

Recently I've been working on some embedded devices, where we have some structs and unions that need to be initialized at compile time so that we can keep certain things in flash or ROM that don't need to be modified, and save a little flash or SRAM at a bit of a performance cost. Currently the code compiles as valid C99, but without this adjustment it used to compile as C++ code as well, and it would be great to support things being compiled that way as well. One of the key things that prevents this is that we're using C99 designated initializers which do not work within the C subset of C++. I'm not much of a C++ buff, so I'm wondering what simple ways there might be to make this happen in either C++ compatible C, or in C++ that still allow initialization at compile time so that the structs and unions not need be initialized after program startup in SRAM.

One additional point of note: a key reason for designated initializer usage is initalizing as NOT the first member of a union. Also, sticking with standard C++ or ANSI C is a plus in order to maintain compatibility with other compilers (I know about the GNU extensions that provide something like designated initializers without C99).

Was it helpful?

Solution

I'm not sure you can do it in C++. For the stuff that you need to initialize using designated initializers, you can put those separately in a .c file compiled as C99, e.g.:

// 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

OTHER TIPS

Building on Shing Yip's answer, and with benefit of 3 year's time, C++11 can now guarantee compile time initialization:

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);

Assembly:

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

    .globl  _bar2                   ## @bar2
    .p2align    2
_bar2:
    .long   1067316150              ## float 1.23399997
#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

In C++ union can have constructors too. May be this is what you wanted?

This is sort of both an answer and a question. I realize this thread is dead, but it exactly what I was looking into tonight.

I did some poking around and the closest thing I can get to what I want (which is similar to what you want... I have been working with pics and have no need to use c++, but I am curious how it might be done) is the first code example:

#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;
}

This has a very similar appearance to the C99 style designated initializers with a caveat I will mention later. (You would probably wrap this in #ifdef __cplusplus if you wanted the struct to be compiled by either.) The second version of code I looked at is this:

#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;
}

Basically, from looking at the disassembly, it appears the first example is actually slower. I have looked at the assembly output and, well, I must be a little rusty. Maybe someone could give me some insight. The assembly output of the first cpp compiled and looked like:

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

The second example looked like:

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

Both of these were generated with a g++ -O0 -S main.cpp command. Clearly, the intuitively less efficient example generated more efficient opcode in terms of number of instructions. On the other hand, there are few cases where I could imagine the few instructions being critical. (On the other hand, I really have trouble understanding assembly not written by humans, so maybe I am missing something... ) I think this provides a solution, albeit late, to the question James asked. The next thing I should test is if the same initialization is allowed in C99; if that works, I think it fully addresses James's problem.

Disclaimer: I have no idea if this works or behaves similarly for any other compilers other than g++.

Dry hole report:

Given

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

I tried deriving S1 from S, where S1's inline default constructor invokes S(int) and passes a hard-coded value ...

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

... and then compiled with gcc 4.0.1 -O2 -S. The hope was that the optimizer would see that s1.mB would necessarily be 22 and assign the value it at compile time, but from the assembler ...

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

... it looks like the generated code does the initialization at runtime prior to main. Even if it had worked, it would hardly be compilable as C99 and would have the kludge of deriving a class for each object you wanted to initialize; so, don't bother.

The following code compiles without problems with 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;
}

Here's the result:

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

The only thing with the C++ initializers is that you need to initialize all elements of the struct or the rest will be initialized with zeros. You can not pick and choose. But that should still be OK for your use case.

One additional point of note: a key reason for designated initializer usage is initalizing as NOT the first member of a union.

For that you need to use the "workaround" shown in the example where I set the "float" member by providing the equivalent int value. It's a bit of a hack, but if it solves your problem.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top