3

In an embedded project, I use a library that provides a macro for initializing a structure. This provides reasonable defaults, but the defaults depend on other parameters. I want to override one or more values of this designated initializer, because initializing the values afterwards generates overhead.

Ideally, I don't want to copy-paste all of the macro's, because I then have to manage third-party code. If the library changes it's defaults, I don't want to do that too.

Is there a way of combining or overriding designated initializers, so there is no overhead? The code has to be C99 compliant and portable.

Some example code to demonstrate the issue:

#if SITUATION
#define LIBRARY_DEFAULTS \
{ \
  .field_a = 1, \
  .field_b = 2, \
  .field_c = 3 \
}
#else
#define LIBRARY_DEFAULTS \
{ \
  .field_a = 100, \
  .field_b = 200, \
  .field_c = 300, \
  .field_d = 400, \
  .field_e = 500 \
}
#endif

/* The following is what I want (or similar), but (of course) doesn't 
   work. */
// #define MY_DEFAULTS = LIBRARY_DEFAULTS + { .field_a = 100 }

int main(void) {
    /* The exact definition of something also depends on situation. */
    struct something library_thing = LIBRARY_DEFAULTS;

    /* This generates overhead, and I want to avoid this. It is certain
       that the field exists. */
    library_thing.field_a = 100;
}
too honest for this site
  • 12,050
  • 4
  • 30
  • 52
BasilFX
  • 43
  • 2
  • 8
  • 1
    There isn't going to be an easy way to do it. If the macro were written as `#define LIBRARY_DEFAULTS { .field_a = 1, .field_b = 2, .field_c = 3, OPTIONAL_USER_DEFAULTS }` where you got to choose whether to define that extra macro, then you could do it (`#define OPTIONAL_USER_DEFAULTS .field_a = 100`) and you'd not get any warning from GCC unless you specified `-Woverride-init` (which is set by `-Wextra` but not by `-Wall`). But the hooking isn't there. You could consider whether you preprocess the library header to provide the hook — but it would be messy. – Jonathan Leffler Feb 20 '16 at 18:37
  • @JonathanLeffler: Wrapping the structure gives a pretty easy way to do it; see my answer. (Designated initializers in C99 are really powerful!) – nneonneo Feb 20 '16 at 18:46
  • What do you mean with "overhead"? There is no overhead code generated. – too honest for this site Feb 21 '16 at 22:06
  • The compiler (arm-gcc-embedded, -Os) generates an additional instruction for `library_thing.field_a = 100`. I call this overhead if I simply want to override a default value, directly after I initialized the defaults. – BasilFX Feb 24 '16 at 08:40

2 Answers2

1

You could wrap your library_thing in an outer structure, and do your overrides from the outer structure's initializer:

#include <stdio.h>

struct foo {
    int a,b,c;
};

#define FOO_DEFAULTS { .a = 1, .b = 2, .c = 3 }

int main() {
    struct {
        struct foo x;
    } baz = {
        .x = FOO_DEFAULTS,
        .x.a = 4,
    };

    printf("%d\n", baz.x.a); // prints 4
}

In fact, you can even do

.x = FOO_DEFAULTS,
.x = {.a = 4},

if you need to really "merge" two initializers.

This compiles fine on Clang (7.0.2) but generates a warning under -Winitializer-overrides. Checking the generated code confirms that the structure is initialized with 4, 2, 3 so there is no additional overhead from this trick.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • Well, it works, but the extra layer means you have to change all the code that used `struct foo x; …; printf("a = %d\n", x.a);` to use `struct baz x; …; printf("a = %d\n", x.x.a);`, which is a quite invasive. If you use pointers everywhere, it wouldn't be so bad — you'd initialize the `struct baz` but use a pointer to the `struct foo` contained in `struct baz` (and the addresses are the same). I will stick with my "there isn't an easy way to do it"; I don't think this qualifies as easy. – Jonathan Leffler Feb 20 '16 at 18:52
  • If you _really_ need a `struct foo`, you could drop the initializer code into a `static struct foo init_foo() {...; return baz.x;}`. A decent compiler would elide the copy and you would just do `struct foo x = init_foo();` in your main function. – nneonneo Feb 20 '16 at 18:56
  • I have wrapped the 'boilerplate' in a macro, which generates the suggested structure. Now I can reuse this solution in several places. Addressing the fields via `.x.field` is acceptable. Therefore, I accepted this answer. – BasilFX Feb 21 '16 at 21:45
1

Here's one possible solution. First remove the braces from the macro

#define LIBRARY_DEFAULTS .a=1, .b=2, .c=3

Then for variables where the defaults are ok, you enclose the macro in braces

struct something standard = { LIBRARY_DEFAULTS };

For a variable where the defaults need tweaking, add some additional initializers

struct something tweaked = { LIBRARY_DEFAULTS, .a=100 };

Why does this work? Section 6.7.9 of the C specification discusses the use of designators in initialization lists and has this to say about specifying the same designator more than once:

19 The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject;151) all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.

and note 151 says this

151) Any initializer for the subobject which is overridden and so not used to initialize that subobject might not be evaluated at all.

Which is to say that the compiler is required to use the last designated initializer, and may implement this solution without any overhead.

user3386109
  • 34,287
  • 7
  • 49
  • 68
  • This only works if you can actually modify the library code - OP suggests that he can't. – nneonneo Feb 20 '16 at 18:57
  • Note that both `clang` and `gcc` set to fussy enough warn about repeated initializers (`-Winitializer-overrides` and `-Woverride-init` respectively, where for GCC `-Wextra` includes the more specific `-Woverride-init`). Also, the problem is that the header is provided by a library vendor, and so the OP needs to use the current version of the library header — simply removing the braces isn't really an option. – Jonathan Leffler Feb 20 '16 at 19:00
  • I would say that OP has the option to remove the braces from the library header, as long as OP doesn't recompile the library code using the modified header. The OP also has the option to copy/paste the `LIBRARY_DEFAULTS` into a project header file. Note that you aren't modifying **library code**, you're modifying a macro that's only intended to be used in user code. – user3386109 Feb 20 '16 at 19:04