C is popular, yet old. Its deficiencies caused C++ to be written to address this. However C has not gone away, and has undergone some parallel evolution to fix some of these problems without completely reinventing itself.
Designated initializers
This is a bit of a mouthful, but it's all about how you can initialize data structures.
Normally with C you define a data structure like:
struct foo {
char *bar;
char *baz;
char *qux;
};
You can initialize it at the point of declaration by giving it values in
{}
braces, as you would pass arguments to a function:
struct foo f = { "bar", NULL, "qux" };
This is annoying, as it means you have to rework all your code when the struct changes, and you may not notice the error, since it is not obvious that the values are wrong.
Alternatively you could initialize the values in code:
struct foo f;
memset(&f, 0, sizeof(f));
f.bar = "bar";
f.qux = "qux";
This would compile to roughly the same code, but it is very verbose, and
If you were using C++ you would define a constructor for the struct, and you wouldn't need to rework all the users of the struct when the order of the fields changes, since the constructor would ensure that it filled out the data in the struct in the correct order.
You can simulate this in C with your own initializer function:
void foo_init(struct foo *f, char *bar, char *baz, char *qux)
{
f->bar = bar;
f->baz = baz;
f->qux = qux;
}
...
struct foo f;
foo_init(&f);
This is a lot more boilerplate to handle though, so the C99 standard added designated initializers as a simpler way to handle this, where you specify named parameters as follows:
struct foo f = { .bar = "bar", .baz = NULL, .qux = "qux" };
Missing fields default to zero, so you can omit the .baz
parameter:
struct foo f = { .bar = "bar", .qux = "qux" };
Duplicated fields are allowable, so other defaults can be handled by putting a macro in.
#define FOO_DEFAULTS .bar = "bar", .qux = "qux"
...
struct foo f = { FOO_DEFAULTS, .bar = "BAR" };
Compound literals
Now we have a handy way to initialize simple data structures, but we can still do better, as functions can take structs as parameters. Currently we have to define it and pass a reference:
int print_bar(FILE *out, struct foo *f)
{
return fprintf(out, "%s\n", f->bar);
}
...
struct foo f = { FOO_DEFAULTS, .bar = "BAR" };
print_bar(stdout, &f);
This is a bit verbose, as constructing the object is trivial. Having a function that returns a reference to the object won't work either, as you would have to malloc(3) the structure, which would be a memory leak unless the function free(3)s the object itself.
struct foo *new_foo(char *bar, char *baz, char *qux)
{
struct foo *f = malloc(sizeof(struct foo));
foo_init(f, bar, baz, qux);
return f;
}
print_bar(stdout, new_foo("bar", "baz", "qux")); /* memory leak */
In C++ you would have the function take the struct by reference, and
pass a constructed object to it without using the new
keyword.
C99 added Compound literals as a solution for this, which use similar syntax to the current initialisation, while still allocating the object on the stack, so you don't need to worry about using free(3) later.
print_bar(stdout, &((struct foo){FOO_DEFAULTS, .bar = "BAR"});
This looks similar to the initializaion syntax, but with the type of the expression prefixed with a cast to the right type.
Cleanup attribute
One of the important things about compound literals is that they are allocated on the stack, so you don't need to worry about having more code to clean it up again.
Resource allocation, however, is not always done on the stack. Normally you would require extra code to clean up your resources.
struct foo *f = new_foo("bar", NULL, "qux");
print_foo(stdout, f);
free(f);
With C++ you would use destructors and the RAII idiom to clean up these objects.
GCC has an extension mechanism called attributes, one of which is the Cleanup attribute.
void freep(void **m)
{
if (*m) {
free(*m);
*m = NULL;
}
}
#define cleanup_freep __attribute__((cleanup(freep)))
...
cleanup_freep struct foo *f = new_foo("bar", NULL, "qux");
print_foo(stdout, f);
This allows you to tag your variable declarations, such that a function is called when the function exits.
This is used to great effect in the systemd codebase to avoid boilerplate for resource handling.