7.8. Index Order

Time: 00:06:23 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)

Previously, I stated that when defining a two-dimensional array, the first size is the number of rows, and the second is the number of columns. I'm deliberately using the labels "rows" and "columns" because their meanings are well-established and understood in the context of tables. But does choosing one array index order over the other make a difference? Are we forced to use rows × cols, or can we define it as cols × rows? Again, I'm deliberately using a multiplication notation here to make a point: multiplication is commutative, so the product rows × cols is the same as cols × rows: both create the same number of elements. This observation suggests that there is some ambiguity, or, depending on your point of view, some flexibility with the index order.

int array[2][3];
int array[3][2];
The amount of memory allocated to store a two-dimensional array is independent of index order. The picture illustrates six array elements implemented as a linear sequence of squares: 2*3 = 3*2 = 6.
Array definitions and memory allocation. The system allocates the same amount of memory regardless of index order. Although the example defines arrays with two dimensions, they exist in computer memory (having a linear address space) as one-dimensional arrays. The compiler must generate machine code that maps the two-dimensional array references in a program to the one-dimensional address needed to access the element in computer memory. The compiler uses a technique called row major ordering, described for the interested reader in a supplemental section at the end of the chapter.

Figure 1 suggests that if we consistently use the same index order throughout the program, either order will work - at least in many cases. Nevertheless, I assert that the "correct" index order is rows × cols or array[rows][cols]. My view may seem capricious in light of what we have just seen, so permit me to offer a few reasons for my assertion.

  1. Rows × columns is a well-accepted practice (see, for example, Multidimensional arrays). Therefore, code that follows the accepted practice is easier for others to read and understand.
  2. In Java, a two-dimensional array is an array of arrays - switching array indexes in a Java program does not work. Programmers working in both languages or converting programs between languages face less confusion and make fewer errors when using the same index order.
  3. When the operating system starts a program, it passes information to it through the command-line interface by filling an array, traditionally named argv, with the information. argv is an array of string pointers, and each string is an array of characters. A two-dimensional notation, argv[row][col], is used to access individual characters in the array. The operating system defines the index order, and programmers cannot change it. (Command line arguments are addressed in detail in the next chapter.)
    The picture illustrates an array of pointers named argv. Each element of argv is a string implemented as an array of characters, and each string has a different length or size.
    An array of string pointers. It is possible, and in some cases quite convenient, to access individual characters using a two-dimensional, two-index notation. For example:
    cout << argv[2][5] << endl;
    prints the character 'r.' It's not possible to reverse the index order.
  4. When printing a two-dimensional array to the console, you must print the elements by rows from the top left to the bottom right because moving the cursor backward or upwards is difficult.
  5. Programs process two-dimensional initializer lists by rows - the values are stored by horizontal rows, from top to bottom.
  6. It's possible to extract individual (horizontal) rows from an array but not individual columns.
  7.  1  2  3  4
     5  6  7  8
     9 10 11 12
    int array[3][4]
    int array[4][3]
    (a)(b)
    #include <iostream>
    using namespace std;
    
    void print_row(int* row, int size)
    {
    	for (int i = 0; i < size; i++)
    		cout << row[i] << endl;
    }
    
    int main()
    {
    	int array[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };	// row first
    	//int array[4][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };	// columns first
    
    	print_row(array[2], sizeof(array[2]) / sizeof(int));		// see the note below
    
    	return 0;
    }
    (c)
    9
    10
    11
    12
    7
    8
    9
    10
    (d)(e)
    Initializing and using two-dimensional arrays. A program that demonstrates initializing a two-dimensional array (reason 5) and extracting a single row from the array (reason 6).
    1. Imagine a problem requiring the programmer to create a two-dimensional array. As a simple illustration, we fill the array with sequential numbers.
    2. The programmer may choose which order to specify the index values - both orders allocate sufficient memory to hold the array.
    3. The program uses a list of values to initialize the array, defines a function that takes and prints a single row, and extracts the row with the index operator in the function call. The operator automatically selects a single row when using only one index with a two-dimensional array. You can only use sizeof to get the size of an array when done in the same function where the array is defined - it does not work with function arguments.
    4. Output from the rows first version.
    5. Output from the columns-first version, likely not the intended result.

In summary, some programs will work regardless of the index order as long as the indexes are used consistently throughout the program. But it is customary to use rows x cols or [rows][cols] and this order always works. So, throughout this textbook, rows first, followed by columns, is the "correct" order.