Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combine designated initializers and malloc in C99+?

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?

like image 341
Matt Joiner Avatar asked Sep 01 '11 02:09

Matt Joiner


People also ask

Does C++ have designated Initializers?

With C++20, we get a handy way of initializing data members. The new feature is called designated initializers and might be familiar to C programmers.

What is designated initializer in Swift?

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.


2 Answers

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.

like image 149
Jens Gustedt Avatar answered Sep 28 '22 08:09

Jens Gustedt


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.

like image 35
Keith Thompson Avatar answered Sep 28 '22 06:09

Keith Thompson