Much discussion is about minimizing the number of allocations.
If the 2D organization of the struct Array is meant to be fixed during the lifetime of the object, only 1 allocation is needed. See below. Changing OP's struct Array is not needed. Rows of data can still be swapped, within the struct Array. Code can continue to conveniently access elements via array->data[r][c] on the left and right of an =.
To accomplish 1 allocation, that allocation needs to account for the space of struct Array, an array of double * and an array of double as well as potential padding.
// Allocate once for the following
Space for `struct Array`
Potential padding
double row[rows];
Potential padding
double vals[rows * cols];
Be warned that it is easy to mess up the calculations.
// Staying with OP's original definition and function interfaces (mostly).
struct Array {
double **data;
size_t rows;
size_t cols;
};
static size_t size_round_up(size_t sz, size_t alignment) {
return (sz / alignment + !!sz % alignment) * alignment;
}
static void* ptr_offset(void *ptr, size_t offset) {
return (char*) ptr + offset;
}
struct Array* NewArray(const size_t rows, const size_t cols) {
size_t array_size = sizeof(struct Array);
size_t rows_offset = size_round_up(array_size, _Alignof(double*));
size_t rows_size = sizeof(double*) * rows;
size_t vals_offset = size_round_up(rows_offset + rows_size, _Alignof(double));
size_t vals_size = sizeof(double) * rows * cols;
struct Array *all = calloc(1, vals_offset + vals_size);
if (all) {
all->data = ptr_offset(all, rows_offset);
all->rows = rows;
all->cols = cols;
double *vals = ptr_offset(all, vals_offset);
for (size_t r = 0; r < rows; r++) {
all->data[r] = vals;
vals += cols;
}
}
return all;
}
void FreeArray(struct Array *array) {
// With one allocation, freeing is simple.
// Tolerate FreeArray(NULL). Just like free(NULL) is OK
free(array);
}