Question

Is there a nice way to combine designated initializers from C99, with the result of a malloc?

The following seems to have needless duplication:

typedef struct {
   int a, b, c;
} Type;

Type *t = malloc(sizeof *t);
*t = (Type) {
    .a = 2,
    .b = 3,
    .c = 5,
};

Can the use of Type, and *t be removed from the above code?

Was it helpful?

Solution

Since you asked ;) there is one tool in C to avoid explicit duplication of code, macros. That said I don't see a way not to repeat at least the name of the type. But in C++ they can't either, so C is at least as good :)

The easiest I see is

#define DESIGNATE_NEW(T)            \
  memcpy(malloc(sizeof(T)),         \
         &(T const){ __VA_ARGS__ }, \
         sizeof(T))

which would give

Type *t = DESIGNATE_NEW(Type,
    .a = 2,
    .b = 3,
    .c = 5,
);

this has several advantages.

  • It initializes all members correctly, even on architectures with non standard representations of the 0 for float types or pointers.
  • Other than Keith' version it is "coding style" acceptable since it is just an expression that looks like an initialization and anybody should immediately capture visually what the second code snipset is supposed to do.

NB: Observe the const in the macro, this allows several instances of the compound literal to be folded, if the compiler decides this to be relevant. Also there are means to have a variant where the list of designators is optional, see P99 below.

The disadvantage is the memcpy and I would be happier with an assignment. Second there is no check for failure of malloc before using the result, but one could probably come across with some weirdness to have the code exit nicely.

In P99 I go a slightly different way. There we always have an initialization function for a type, something like

inline
Type* Type_init(Type* t, int a, int b, int c) {
  if (t) {
    *t = (Type const){ .a = a, .b = b, .c = c };
  }
  return t;
}

which by macro magic can be made to provide default arguments for a, b and c if they are omitted. Then you can simply use something like

Type *t = P99_NEW(Type, 1, 2, 3);

in your application code. This is better, since it avoids dereferrencing the pointer when the call to malloc failed. On the other hand this reintroduces an order to the initializers, so not perfect either.

OTHER TIPS

You can with a variadic macro. I'm not going to claim that this is a good idea, but it works:

#include <stdlib.h>
#include <stdio.h>

#define CREATE(type, ptr, ...) \
    type *ptr = malloc(sizeof *ptr); \
    if (ptr) *ptr = (type){__VA_ARGS__}

int main(void)
{
    typedef struct {
        int a, b, c;
    } Type;
    CREATE(Type, t, .a = 2, .b = 3, .c = 5);
    printf("t->a = %d, t->b = %d, t->c = %d\n", t->a, t->b, t->c);
    return 0;
}

Note that I wasn't able to use the usual do { ... } while (0) macro definition trick (it would create a new scope, and t wouldn't be visible), so you'd have to be careful about the context in which you use this.

Personally, I think I'm happier with the needless duplication.

No, that's the only way to use designated initializers. Without the (Type){}, the compiler doesn't know how to validate the contents.

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