The memory of a computer is essentially a one-dimensional array--the memory address is the array subscript. Therefore, the most natural way to implement a multi-dimensional array is to store its elements in a one-dimensional array. In order to do this, we need a mapping from the n subscript expressions used to access an element of the multi-dimensional array to the one subscript expression used to access the one-dimensional array. E.g., suppose we have a two-dimensional array of elements of type T, T a[2][3], the elements of which are to be stored in a one-dimensional array, T b[20]. Then we need to determine which element of b, say b[k], will be accessed given a reference of the form a[i][j]. I.e., we need the mapping f such that .
The mapping function determines the way in which the elements of the array are stored in memory. The most common way to represent an array is in row-major order , also known as lexicographic order . E.g., consider the 2D array T a[2][3]. The row-major layout of this array is shown in Figure .
Figure: Row-Major Order Layout of a 2D Array
In row-major layout, it is the right-most subscript expression (the column index) that increases the fastest. As a result, the elements of the rows of the matrix end up stored in contiguous memory locations. In Figure , the array is stored starting from address a. The first element of the first row is a address . The first element of the second row is at address , since there are 3 elements in each row.
We can now generalize this to an arbitrary n-dimensional array. Suppose we have an n-D array declared as T a[ ][ ][ ][ ]. Furthermore, let a be the starting address of the array. Then, the address of the element a[ ][ ][ ][ ] is given by
where
The running time required to calculate the address appears to be since the address is the sum of n terms and for each term we need to compute , which requires O(n) multiplications in the worst case. However, the address calculation can in fact be done in O(n) time using the following algorithm:
unsigned int product = 1; T* address = ; for (int j = n; j >= 1; -j)address += product * ; product *= ;
This algorithm makes subtle use of the way that address arithmetic is done in C++. Since the variable address is of type T*, it is not necessary to scale the computation by sizeof(T). In C++ whenever an integer value is added to a pointer variable, it is automatically scaled by the compiler before the addition.