In two ancient posts I have talked about arrays in modern C, “don’t be afraid of variably modified types” and “VLA as function arguments”. Still there seem to be a lot of people that, perhaps just by bad habit, that prefer to use “fake matrices” instead of real matrices in C. Unfortunately among these people are a lot of university teachers that preach that bad parole to their students. I just try to make a list of the advantages of real matrices, here.
First of all, what do I mean by “fake matrices”? These are arrays of pointers that are used to emulate matrices. In the following N
is a potentially large value:
double* fakeMatrix[N] = malloc(N*sizeof(double*)); for (size_t i = 0; i < N; ++i) fakeMatrix[i] = malloc(N*sizeof(double)); for (size_t i = 0; i < N; ++i) for (size_t j = 0; j < N; ++j) fakeMatrix[i][j] = 0.0; /* Use fakeMatrix for something */ for (size_t i = 0; i < N; ++i) free(fakeMatrix[i]); free(fakeMatrix);
We see with the initialization statement that fakeMatrix
can indeed be use as a matrix, but its allocation and deallocation are crude and error prone. Before C99, such difficult code was necessary, as soon as N
was not a constant but a variable.
Now, here is the equivalent code for real C matrices:
double (*realMatrix)[N] = malloc(sizeof(double[N][N])); for (size_t i = 0; i < N; ++i) for (size_t j = 0; j < N; ++j) realMatrix[i][j] = 0.0; /* Use realMatrix for something */ free(realMatrix);
The advantages:

They are conceptually simple

The allocated memory is contiguous:
 The allocation “algorithm” is efficient, in O(1)
 Memory access is efficient, since there are no gaps between matrix lines

One simple allocation and deallocation
 no need to declare auxiliary functions.

The compiler does the calculus of indices.
 the target of
realMatrix[i][j]
can be addressed in one go without using indirection.
 the target of

For the fake matrix the idea that all lines of the matrix have the same length is only implicit, the type is underspecified.

The allocation method
malloc(sizeof(double[N][M]))
is clearer compiler even does the computation of the size within the correct type, namely
size_t
even if the expressionN*N
(withN
wrongly chosen asint
) would overflow.
 compiler even does the computation of the size within the correct type, namely