HACKER Q&A
📣 pkkm

C programmers, do you prefer to initialize in place or allocate?


An API for a data structure in C could be designed in three ways. It could allocate memory by itself and return a pointer, like so:

  typedef struct {
      char *name;
      int *adj_matrix;
      ...
  } MyGraph;

  void my_graph_create(const char *name, int n_vertices) {
      MyGraph *result = malloc(sizeof(*result));
      result->name = strdup(name);
      result->adj_matrix = calloc(...);
      ...
      return result;
  }
it could initialize the struct in place, but allocate memory for the dynamically-sized parts:

  void my_graph_init(MyGraph *result, const char *name, int n_vertices) {
      result->name = strdup(name);
      result->adj_matrix = calloc(...);
      ...
  }
or it could make the user responsible for all memory allocation:

  size_t my_graph_adj_matrix_size_needed(int n_vertices);

  void my_graph_init(MyGraph *result, char *name, int *adj_matrix) {
      result->name = name;
      result->adj_matrix = adj_matrix;
      ...
  }
Which style do you prefer? Can you explain what makes you prefer it? Is it different between external APIs that you'll export from a library and internal APIs that will only be used by other parts of your code?


  👤 simonblack Accepted Answer ✓
These days, I would mainly (95%) allocate all fixed-size variables, arrays, etc at compile-time. And only malloc stuff that needs to be sized at run-time. I just find it's easier. And I'm lazy: I don't waste time optimising stuff that's not critical.

But, once upon a time, RAM was horribly expensive ($25 per KILObyte in my own experience) and it made a lot of sense to malloc most the structs, arrays, etc. However, those days are long gone and who cares if my program takes up a few more tens of MEGAbytes on startup. Most of that will be paged out by the operating system, anyway.

Most initialisation is to (belt and braces) zero stuff out at compile-time, even if the compiler will zero it out anyway. Heap stuff will be zeroed out specifically also when invoking the function - function variables will be set to specific values, usually zero. ("int x = 0;")

Large arrays and structs that require 'set and forget' initialisation such as tables, binary-blobs, strings, etc are handled at compile time by using header files.


👤 lesserknowndan
My preference is for A. That said, I am considering using the following form, which allows the self MyGraph pointer to be null, and will cause the constructor to allocate the space required. This also allows the constructor to be called by some other function that wants it to initialise some pre-allocated memory.

MyGraph*

MyGraph_New( MyGraph* self, const char* name, const Matrix* m )

{

    if ( !self ) self = calloc( 1, sizeof( MyGraph ) );

    if ( self )
    {
        self->name   = strdup( name );
        self->matrix = Matrix_copy( m );
    }
    return self;
}