Multi-dimensional arrays are represented in the same way as one-dimensional arrays: by a pointer holding the base address and pointing to a contiguous statically allocated segment where all the array's items are stored. The array's dimension is a logical concept, not a physical one, and the compiler translates multi-dimensional access to the array items into ordinary one-dimensional access to the underlying one-dimensional array by using the so-called row-major formula. In other words, the array is stored as a one-dimensional array using the row-major format. For the two-dimensional case, the row-major format means that rows of the array are stored consecutively. In order to do this, the compiler must know the sizes of all indexes of the array (except for the very first one), and the programmer must deliver this information through appropriate definitions or declarations that include the explicit sizes of all indexes (save the first).
Because multi-dimensional arrays are actually stored and accessed as one-dimensional arrays, they exhibit the same behavior when passed to functions and so behave as if passed by reference.
For dynamic multi-dimensional arrays, we cannot rely on the automatic indexing that translates directly to indirection (as in the one-dimensional case), for the compiler lacks information about the explicit size of all indexes except the very first one. Thus, in the multi-dimensional case we create dynamic arrays of dynamic arrays in order to obtain the right kind of behavior with respect to indexing.