I was wondering what would be the best approach to allocate/deallocate multiple one-dimensional, dynamic arrays in C. This seems easy at first, however, for me it turned out to be problematic. Consider the following example program, which illustrates my problem:
#include <stdio.h>
#include <stdlib.h>
#define N 10
int my_func(size_t n);
int main(void)
{
int ret = my_func(N);
printf("%d\n", ret);
return 0;
}
int my_func(size_t n)
{
double *x = malloc(n * sizeof (double));
if (x==NULL) return 0;
double *y = malloc(n * sizeof (double));
if (y==NULL) { free(x); return 0; }
double *z = malloc(n * sizeof (double));
if (z==NULL) { free(x); free(y); return 0;}
/* some computations */
free(x); free(y); free(z);
return 1;
}
Allocation and checking of x
, is straightforward. However, if the allocation of y
fails, one has to free x
. If there is a further array, like z
in the example, and its allocation fails, one has to care about x
and y
. And as always, all memory has to be freed at the end of the function. Generally, checking for a failed allocation somewhere in the program requires to take care of all previously allocated memory blocks.
I thought it would be "easier" to automate the deallocation of all previously allocated memory blocks, so I came up with the following implementation that uses a "memory registry" to store pointers to the allocated memory blocks.
#include <stdio.h>
#include <stdlib.h>
#define N 10
#define N_BLOCKS 3
double *mem_reg[] = { NULL, NULL, NULL };
void clean_up(void);
int my_func(size_t n);
int main(void)
{
int ret = my_func(N);
printf("%d\n", ret);
return 0;
}
int my_func(size_t n)
{
double *x = malloc(n * sizeof (double));
if (x!=NULL) mem_reg[0] = x; else goto fail;
double *y = malloc(n * sizeof (double));
if (x!=NULL) mem_reg[1] = y; else goto fail;
double *z = malloc(n * sizeof (double));
if (x!=NULL) mem_reg[2] = z; else goto fail;
/* some computations */
clean_up();
return 1;
fail:
clean_up();
return 0;
}
void clean_up(void)
{
for (size_t i=0; i<N_BLOCKS; i++)
{
if (mem_reg[i] != NULL)
free(mem_reg[i]);
}
}
The advantage of the second example is that there is one single exit point in my_func
defined under the label fail
. So even if I had to check for some errors in the computations (not shown in the examples), I could simply goto fail
in order to easily clean up all dynamic allocations. The disadvantage is that the code of the second example is not easier than the code of the first example.
You may have recognized that I am not so much experienced in C, hence this question. Is there an advantage in one of the both examples, that I didn't see? Then which one is to be preferred. Or is there even a better way?
goto
is fine; especially within the above example. Linux source is full of gotos, likewise the CPython source. I found this thread a good resource.