ccl
ccl

Reputation: 75

C struct of structs initialization

I'm a little confused about the best way to initialize complex structs (struct of struct)

I basically have a setup like this

typedef struct {
    float x;
    float y;
} Vectorf;

typedef struct {
    Vectorf position;
    Vectorf direction;
    Vectorf velocity;
} Player;

What would be the best way to initialize a Player object?

Variant A

Vectorf position = {1.0f, 1.0f};
Vectorf direction = {1.0f, 0.0f};
Vectorf velocity = {0.0f, 0.0f};

Player player = {position, direction, velocity};

Variant B

Player *player = malloc(sizeof(Player));

player->position.x = 1.0f;
player->position.y = 1.0f;
player->direction.x = 1.0f;
player->direction.y = 0.0f;
player->velocity.x = 0.0f;
player->velocity.y = 0.0f;

--- Stuff ---

free(player);

Or even create a function like

Player *createPlayer(float px, float py...)
Player createPlayer(float px, float py...)

But inside these I would need Variant A or B again I guess.

Is it just a matter of taste or are there benefits for either?

I also have something like this

typedef struct {
    int x;
    int y;
    Texture *texture;
    bool walkable;
} Tile;

typedef struct {
    int width;
    int height;
    Tile *tiles;
} Map;

Here a create function seems more reasonable, because I can pass actual map data.

Upvotes: 4

Views: 535

Answers (3)

Pablo
Pablo

Reputation: 13580

I think that answer should be depends on your needs, both ways of creating objects are OK.

Sometimes you don't need to worry about allocation memory with malloc if you are going to use an object only a couple of times and you don't need that the objects "survives" when the function exits. So your variant A would be OK.

Sometimes you want to create a lot of objects and store them in other data structures (lists, trees, etc), and you need the objects to "live" throughout the whole program. In this case your variant B would be the better. AnT's answer shows you how you could save lines of code for initialization.

The other time when I consider using malloc is when I know that the struct is very large and would consume lots of bytes for each object, so having a lot of them could potentially eat up all your stack. In that case I'd rather have these large objects in the heap, and also handling with pointers is much cheaper than having to creates copies when calling function that do not take pointers.

I also use malloc when I see the structs as classes and I want a clean API for creating, using and destryoing them. I always use prefix for the functions and I always have a create, init and free function for every object type, something like this:

typedef struct abc {
    // lot's of members
} Abc;

Abc *abc_create(void);
int abc_init(Abc *abc); // always with default values
void abc_free(Abc *abc);

int abc_do_A(Abc *abc, int x);
int abc_do_B(Abc *abc, int y);
....

and the three first functions usually look like this:

Abc *abc_create(void)
{
    Abc *abc = calloc(1, sizeof *abc);
    if(abc == NULL)
        return NULL;

    if(abc_init(abc) == 1)
        return abc;

    free(abc);
    return NULL;
}

int abc_init(Abc *abc)
{
    if(abc == NULL)
        return 0;

    // initializations
    ...

    return 1;
}

void abc_free(Abc *abc)
{
    if(abc == NULL)
        return;

    // do free of other malloced
    // members if present
    free(abc);
}

I think this gives you a very clear and easy API to use. I maintain a C library at work where I have at least 50+ of these structs, all with the same scheme, using them it's easy because they all behave like this:

Abc *abc = abc_create(1, 2, 3);
if(abc == NULL)
{
    error handling
}

abc_do_A(abc, 12);
abc_do_A(abc, 11);
...
abc_free(abc); // when not needed anymore

Upvotes: 2

AnT stands with Russia
AnT stands with Russia

Reputation: 320541

For Variant A

Player player = { { 1., 1. }, { 1., 0. }, { 0., 0. } };

You can also use a tagged variant for better clarity and readability, as in @dev_null's answer.

For Variant B compound literals will help you

Player *player = malloc(sizeof *player);

*player = (Player) { { 1., 1. }, { 1., 0. }, { 0., 0. } };

If you have a memdup-like functionality at your disposal, you can implement Variant B as an one-liner

/* Assuming `void *memdup(const void *src, size_t size);` */

Player *player = memdup(
  &(Player) { { 1., 1. }, { 1., 0. }, { 0., 0. } },
  sizeof *player);

Again, feel free to use a tagged version, if you like it better.

Compound literals allow you to mix-and-match your original approach and {}-based approach, if you for some reason decide that it is beneficial

Player *player = malloc(sizeof *player);

player->position = (Vectorf) { 1., 1. };
player->direction = (Vectorf) { 1., 0. }; 
player->velocity = (Vectorf) { 0., 0. }; 

Upvotes: 0

dev_null
dev_null

Reputation: 1997

Player player = {.position = {1.0f, 1.0f}, 
             .direction = {1.0f, 0.0f},
             .velocity = {0.0f, 0.0f}};

Note this won't work in C++. Only in C.

Upvotes: 2

Related Questions