Why did coder assign value of struct pointer to a static struct?

Viewed 108

Apologies if this is a duplicate, in which case I couldn't find the right keywords to search.

This is in reference to some old (MUD) code I'm working on which is pasted below. I'm confused by purpose of the foo_zero and *foo = foo_zero parts of the code below. This is a pattern it uses throughout the codebase. My guess is that it's a way of initializing all of the members of foo to zero/NULL without having to explicitly set them.

typedef struct FOO {
    int buzz;
    char *bazz;
} FOO;

FOO *init_foo(void)
{
    static FOO foo_zero;
    FOO *foo;
    
    foo = malloc(sizeof(*foo));
    *foo = foo_zero; // <-- why?
    return foo;
}
3 Answers

Yes, the lines

static FOO foo_zero;

and

*foo = foo_zero;

arrange that every new instance of struct FOO allocated by init_foo() is initialized just as if someone had said

struct FOO new_foo = { 0 };

Specifically, all integer fields will be initialized to 0, all floating-point fields will be initialized to 0.0, and all pointer fields will be initialized to null pointers (aka NULL, or nullptr in C++).

This is a nice technique, because it's both simpler and, strictly speaking, more portable than other techniques.

There's a discussion percolating in the comments about the alternative possibilities of doing

foo = malloc(sizeof(*foo));
memset(foo, 0, sizeof(*foo));

or

foo = calloc(1, sizeof(*foo));

Both of these would initialize the brand-new struct FOO to all-bits-0. The subtle problem here -- which is so subtle that many programmers would not call it a problem at all -- is that it is theoretically possible for a processor and/or operating system to represent a floating-point value of 0.0, or a null pointer, with a bit pattern of something other than all-bits-0.

But if you're on such a processor, then doing

float f = 0;

or

char *p = 0;

will do the right thing, initializing the variable with the proper zero value, even if it's not all-bits-0. And for an aggregate such as struct FOO, doing

struct FOO new_foo = { 0 };

is equivalent to explicitly initializing each of its members with 0, meaning you get the proper zero value, even if that's not all-bits-0. And, finally, any time you declare a variable with static duration, as in

static FOO foo_zero;

you get an implicit initialization as if you'd said = { 0 };, and therefore the default (static) initialization, too, gives you those correct zero values no matter what.

If you're still curious about calloc's all-bits-0 guarantee, you can read a bit more about it in question 7.31 of the C FAQ list.

In fact this declaration

static FOO foo_zero;

is equivalent to the following

static FOO foo_zero = { .buzz = 0, .bazz = 0 };

So in this assignment statement

*foo = foo_zero;

an object pointed to by the pointer foo is zero initialized the same way as the static variable foo_zero.

The function return a pointer to a zero initialized object.

For this simple case you could achieve almost the same effect if instead of malloc you used calloc.

FOO *init_foo(void)
{
    return calloc( 1, sizeof( struct FOO ) );;
}

But sometimes a non-trivial initialization is required. So the approach you showed has a meaning. For example

struct FOO
{
    size_t n;
    char s[10];
};

struct FOO * init_foo( void )
{
    static struct FOO default_foo = { .n = 6, .s = "Hello" };
    
    struct FOO *foo = malloc( sizeof( *foo ) );
    
    if ( foo ) *foo = default_foo;
    
    return foo;
}

Why complicating?

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

typedef struct {
    int buzz;
    char *bazz;
} FOO;

init_foo() above actually just returns new foo made on the heap. All clean. So let's just do that:

static inline FOO * new_foo(void)
{
    return calloc(1,sizeof(FOO));
}

Usage and check:

int main (void)
{
    FOO * foo = new_foo();

   printf("buzz: %d, bazz: %s", foo->buzz, foo->bazz );

   free(foo);

    return 42;
}

Godbolt shows new struct FOO nicely empty

Program returned: 42
buzz: 0, bazz: (null)
Related