views:

957

answers:

4

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

+5  A: 

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
Adam Rosenfield
+1 Hammers for nails, screwdrivers for screws.
Thomas L Holaday
James Snyder
+2  A: 

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.

lothar
That's only because 42L is implicitly cast to an integer. If you wanted to initialize the float member to, say, 3.5, you can't do that in C++.
Adam Rosenfield
You can not initialize more than one member of a union. It's a union after all ;-) However if you want to initialize the "float" part you will need to initialize it with the integer equivalent (probably as a hex number)
lothar
Yes -- but C99's designated initializers let you initialize an element of a union other than the first without resorting to hacks such as figuring out a float's implementation-defined integer equivalent.
Adam Rosenfield
I'm not asking to initialize more than one member, just not the first one all the time. The reason it's a bit more complicated than doing things with equivalents is that some of them are more complex members of the union. I could point to our sources (modified Lua to run on small devices), but it takes a little digging to understand how things work.If there's interest, here's a starting point: http://svn.berlios.de/svnroot/repos/elua/trunk/src/lua/lrotable.hDescription (yeah, it shows up raw, docs aren't elsewhere yet): http://svn.berlios.de/svnroot/repos/elua/trunk/doc/en/arch_ltr.html
James Snyder
Well if you want to compile it as standard C++ I am afraid you need to use hacks like the "equivalents". And you will only need them for the data that goes into ROM, for all other data that goes into RAM you can create regular constructors like others have pointed out.
lothar
A: 

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.

Thomas L Holaday
Thanks. It doesn't necessarily have to compile as C99. The initializers are in a number of places, but they're defined using some macros, so something where I can ifdef __cplusplus with a minimal modification, that's fine as well.
James Snyder
A: 
#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?

Shing Yip
Is the initialisation done at runtime or compile time? That's, I suppose, the critical issue here.
James Snyder
I'll have to consult the holly standard to be sure, but just off my head, i think all global static variables are initialized and stored in the data section of executable image. Your best bet is to try it out with your compiler and see what it does.
Shing Yip