Search code examples
cc-preprocessorstatic-initialization

Plain "C" static initializer macro with variable length tail.


I have a struct defined as:

typedef struct coro_context {
    int id;
    jmp_buf env;
    list_head list;
    jmp_buf waiter;
    long timeout;
    void *private;
    char stack[0];
} coro_context;

which I need to initialize with three values

  • id = 0
  • private = function pointer
  • append to end of struct a suitable char array of length size

My current attempt looks like (the "unsigned long" bit is to get the right alignment):

#define CORO_CONTEXT(name,size) \
        unsigned long __##name##_ctx[(sizeof(coro_context)+size)/sizeof(unsigned long)]={0};\
        coro_context *name##_ctx = (coro_context *)__##name##_ctx

This works, but has two problems (ok, problems are one and a half ;) ):

  • it is UGLY (half problem).
  • I see no way to statically initialize private = name.

Note: I insist on "static initialization" because I want to use this in plain "C" (c11 is ok, if required) because I need to use these initialization outside function context.


Solution

  • I'd use a union overlaying the struct coro_context instance with a char buffer:

    #include <setjmp.h>
    typedef struct coro_context {
        int id;
        jmp_buf env;
        list_head list;
        jmp_buf waiter;
        long timeout;
        void *private;
        char stack[]; /*should be [] in C11, [0] is not valid C */
    } coro_context;
    
    /*shouldn't start globals with underscore*/
    /*shouldn't violate aliasing rules*/
    /*C11 compound literals obviate the need for an extra global identifier*/
    #define CORO_CONTEXT(name,size) \
        coro_context *name##_ctx = &(union { coro_context ctx; char buf[sizeof(coro_context)+size]; }){ .ctx={ .private=&name } }.ctx;
    
    int my_name;
    
    CORO_CONTEXT(my_name,32)
    

    and then take the address of .ctx. That should also get rid off the aliasing issues your solution has.

    You'll need to initialize with an address of a global, because the value of a global is not usable in static initializations.

    If you want something C99 compatible, you will need the extra identifier, but it shouldn't start with two underscores:

    #include <setjmp.h>
    typedef struct coro_context {
        int id;
        jmp_buf env;
        list_head list;
        jmp_buf waiter;
        long timeout;
        void *private;
        char stack[1]; /*c99 doesn't have flexible array members*/
    } coro_context;
    
    #define CORO_CONTEXT(name,size) \
        union { coro_context ctx; char buf[sizeof(coro_context)+size]; } name##_ctx__ = { { 0, {0},{0},{0}, 0, &name } }; \
        coro_context *name##_ctx = &name##_ctx__.ctx;
    
    int my_name;
    
    CORO_CONTEXT(my_name,32)