CS 111 Assignment 7
Tutorial on two-dimensional arrays
- Two-dimensional arrays.
Arrays can be defined for any data type, including other arrays. An array whose elements are other arrays is known as a multi-dimensional array. If multi-dimensional array's element arrays, in turn, are arrays of elements of a non-array data type, then the multi-dimensional array is a two-dimensional array.An example of a two-dimensional array is the array of command-line arguments. The command-line arguments are an array of C-strings. Each C-string, in turn, is an array of characters. Thus, the command-line arguments are an array whose elements are arrays of characters, i.e. a two-dimensional array of characters.
The smaller arrays inside a two-dimensional array (e.g. the individual C-strings within the array of command-line arguments) are known as the inner array. Its length is known as the inner dimension. The larger array which contains the inner arrays as elements is known as the outer array. Its length (i.e. the number of inner arrays it has room for) is known as the inner dimension.
When we speak of the elements of a two-dimensional array, we usually mean the non-array elements of the inner arrays. To access one of these elements, we need two indices, one for the inner array's location within the outer array, and another one for the element's location within the inner array. For xample, in checkNonAscii3.cpp in the Assignment 4 examples, we accessed the character at location i in argv[1] as follows:
// Search for a non-ASCII character: for ( int i = 0; argv[1][i] != '\0' && !nonAsciiFound; i++ ) if ( argv[1][i] < 0 ) nonAsciiFound = true;A two-dimensional array can be thought of, conceptually, as a two-dimensional table with rows and columns, where the rows are numbered from 0 up to one less than the number of rows, and the columns are numbered from 0 up to one less than the number of columns. We can access the non-array element at row i and column j of a two-dimensional array named numbers as follows:
numbers[i][j]To access all the elements of a two-dimensional array, we typically use a nested loop. For example, if ROWS is the number of rows and COLUMNS is the number of columns, we can set all the elements of array numbers to zero as follows:
for ( int i = 0; i < ROWS; i++ ) for ( int j = 0; j < COLUMNS; j++ ) numbers[i][j] = 0;Although it's convenient to think of a two-dimensional array as a two-dimensional table, that's not how it is actually stored in memory.
In C++, there are two distinct kinds of two-dimensional arrays, which are stored in different ways. One kind is stored as one big one-dimensional array that can be thought of as being subdivided into chunks, where each chunk corresponds to a row. In other words, it's an array of smaller arrays that are stored directly next to each other in memory, as one big array. The other kind of two-dimensional array is a one-dimensional array of pointers to one-dimensional arrays, which may be various different places in memory.
To visualize the two kinds of two dimensional arrays, please refer to the diagrams that were presented in lecture.
in this course, the one-big-array style will be the only kind of two-dimensional array you'll be expected to learn how to create. However, the array of command-line arguments is the other kind of two-dimensional array, an array of pointers to arrays. And you should be aware that arrays of pointers to arrays are more commonly used, by real-life programmers, than the one-big-array style of two dimensional array, because arrays of pointers to arrays are much more flexible in various ways. But they are also more complicated to create, so you won't be taught how to create them until next semester.
It is simple to create a two-dimensional array of the one-big-array style. You just declare it. For example, to declare a two-dimensional array of floating-point numbers named numbers, with 5 rows and 4 columns, we would type:
float numbers[5][4];In terms of how the array is actually stored in memory, the number of rows is also known as the outer dimension, i.e. the number of chunks, and the the number of columns is also known as the inner dimension, i.e. the number of elements per chunk. In the one-big-array style of two-dimensional array, the chunks are always of equal size.
So that we can keep track of the dimensions more easily, it is considered good programming practice to use named constants rather than numeric literals when declaring the array. For example:
const int ROWS = 5; const int COLUMNS = 4; float numbers[ROWS][COLUMNS];As is also the case with one-dimensional arrays, when you create a two-dimensional array by declaring it, it contains garbage until you assign values to all the elements.
To create a two-dimensional array which does not initially contain garbage, we can initialize it using nested initializer lists, as follows:
int array[][3] = { {1, 2, 3}, {0, 4, 5} };Or, so that we can more easily keep track of the dimensions later in our program:
int ROWS = 2; int COLUMNS = 3; int array[][COLUMNS] = { {1, 2, 3}, {0, 4, 5} };When initializing a two-dimensional array, it is necessary to specify, in square brackets, the inner dimension (the number of elements per chunk) but not the outer dimension (the number of chunks).
More generally, when initializing a multi-dimensional array, it is necessary to specify, in square brackets, all dimensions except the outermost. For example, to initialize a three-dimensional array:
const int LAYERS = 3; const int ROWS = 2; const int COLUMNS = 4; int array3Dim[][ROWS][COLUMNS] = { { {1, 2, 3, 4}, {0, 4, 5, 6} }, { {8, -1, 3, 7}, {-4, 6, 7, 1} }, { {10, 3, 8, 2}, {9, 7, 5, -3} }, };(In the above example, LAYERS is the outermost dimension and COLUMNS is the innermost dimension.)
- A partially-filled two-dimensional array.
Compile quizScores1.cpp by typing:g++ quizScores1.cpp textUtility2.cppand run it. This program reads students' ID numbers and 5 quiz scores per student from text data file quizScores.txt and prints them to output text data file quizAverages.txt, to which the program also prints: (1) each student's quiz average , (2) the average of all the students' scores for each quiz, and (3) the average of all the quizzes for all the students.
Our sample input data file quizScores.txt looks like this:
012345678 10 9 8 7 6 987654321 10 9 9 10 9 789012345 9 8 7 7 9 654321987 9 9 9 10 10 123456789 10 8 7 5 8 135792468 9 9 9 9 9where the leftmost column contains student ID numbers, and all other columns contain quiz scores. We want the corresponding output file quizAverages.txt to look like this:
012345678 10 9 8 7 6 8.0 987654321 10 9 9 10 9 9.3 789012345 9 8 7 7 9 8.0 654321987 9 9 9 10 10 9.3 123456789 10 8 7 5 8 7.5 135792468 9 9 9 9 9 9.0 9.5 8.6 8.1 8.0 8.5 8.5where (1) the rightmost column contains the quiz average for each student, (2) the bottom row contains the class average for each quiz, and (3) the bottom right corner is the average score of all students for all quizzes.
To facillitate both row-average and column-average calculations, this program uses a two-dimensional array. Each row of the array will contain all the quiz scores for one student. Each column of the array will contain all the students' scores for one quiz. In quizScores1.cpp, the 2-dimensional array is declared as follows:
int scores[MAX_NUMBER_OF_STUDENTS][NUMBER_OF_QUIZZES];where MAX_NUMBER_OF_STUDENTS and NUMBER_OF_QUIZZESwas declared earlier in the program as named constants:
const int MAX_NUMBER_OF_STUDENTS = 30; const int NUMBER_OF_QUIZZES = 5;Assume that this program is being used by a teacher who always gives 5 quizzes every semester, but the number of students in the class varies from one semester to the next. Assume also that there are never more than 30 students in the class, though the exact number is unknown to the writer of the program.
Since the array must be declared to be a particular size before we start reading values into it from the text file, the array will be partially filled. If there are fewer than 30 students in the class, some of the rows in the array will not be given values. As our program reads information into the array, it will use a variable numberOfStudents to keep track of the actual number of students, as distinct from MAX_NUMBER_OF_STUDENTS, the number of rows in the array.
However, within each of those rows that do correspond to actual students, we will assume that all five elements will be given values. Hence each such row will be completely filled.
Since we know in advance how many scores there will be in each row, if there are any scores there at all, we can use count-controlled repetition to put values in each row. But, since the program does not know in advance how many students will be listed in the input file, the loop which reads all the rows must be event-controlled, stopping at end-of-file. So, to read numbers from the input file into the array, we use a nested loop with an outer while loop and an inner for loop.
The while loop is controlled by the error state of an ifstream object. End-of-file is detected when the program attempts to read the first item on a nonexistent line of the file. Since that first item is an ID number, our program attempts to read an ID number before the error state is checked in the while loop condition. Thus, or program attempts to read an ID number both (1) immediately before the while loop begins and (2) as the very last step within the while loop body.
The ID number is read as a string (a C++ string class object) without bothering to convert it to an int. Once an ID number has been successfully read, nothing else is done with it except to print it to the output file. Hence there is no need to convert it to an int. Furthermore, NOT converting it to an int saves us the trouble of having to reinsert a leading zero when printing it, if the ID number began with a zero.
Each time through the while loop, after the most recently read ID number has been printed to the output file, the current student's scores are read by the inner for loop into a row of the 2-dimensional array scores and then printed to the output file. Each time a score is read, it is added to a cumulative sum of that one student's scores. When the for loop is finished for that row, the cumulative sum is divided by NUMBER_OF_QUIZZES to compute the student's quiz average, which is stored in an element of one-dimenstional array rowAverages, to keep track of all the studnets' quiz averages, and also printed to the output file, at the end of the line.
The variable numberOfStudents, which keeps track of the number of rows of two-dimensional array scores containing actual data, is initialized to zero before the beginning of the while loop and incremented each time through the while loop. The variable numberOfStudents also keeps track of the number of elements of one-dimensional array rowAverages which contain actual data.
After the while loop is finished, i.e. after all the scores have been read and printed and all the row-averages computed and printed, then the program computes the column-averages. This is done using a nested for loop, where the inner loop processes all the scores in one column, and the outer loop processes all the columns of scores. The inner loop iterates over scores in only numberOfStudents rows, i.e. the rows that contain actual data, rather than all MAX_NUMBER_OF_STUDENTS rows in the array.
Then, finally, the total class average is computed, by averaging all the row-averages. This is done using a single for loop.
- When to use two-dimensional arrays.
As it turns out, we didn't really need a two-dimensional array in the above program.Compile quizScores2.cpp by typing:
g++ quizScores2.cpp textUtility2.cppand run it. Observe that quizScores2.cpp behaves exactly the same as quizScores1.cpp. However, quizScores2.cpp does not use a two-dimensional array. Instead, it uses a much smaller one-dimensional array, thereby saving considerable memory space.
In general, you should try to write programs that don't use huge amounts of memory space unnecessarily. Thus, for example, you should avoid using arrays in situations where a single variable of the array's element type would do the job just as well, and, similarly, you should avoid using a two-dimensional array in situations where a much smaller one-dimensional array would do the job just as well.
For an example of a program that actually does need a two-dimensional array, compile quizScores3.cpp by typing:
g++ quizScores3.cpp textUtility2.cppand run it, and look at its output file quizAveragesCurved.txt This program not only computes averages but also curves the scores.
- Two-dimensional arrays (the array-of-pointers kind) as parameters to functions.
The two kinds of two-dimensional arrays, the one-big-array kind and the arry-of-pointers kind, are treated the same when we access their elements. To access an element of either kind of two dimensional array, we type:arrayName[rowIndex][columnIndex]However, the two kinds of two dimensional arrays are created differently. In this tutorial, you have been shown only how to create the one-big-array kind, not the array-of-pointers kind, which you will be shown how to create next semester.
The two kinds of two-dimensional arrays are also declared differently as parameters to functions. Later in this tutorial, you will be shown how the one-big-array style of two-dimensional array is declared as a parameter. We will now discuss how the array-of-pointers style of array is declared as a parameter and passed as an argument.
You have seen how the command-line arguments are declared in the main function of programs which use command-line arguments. The following two notations are equivalent:
int main(int argc, char* argv[])and
int main(int argc, char** argv)The array-of-pointers style of two-dimensional array is, in effect, a one-dimensional array of pointers to other one-dimensional arrays. As we saw in earlier examples involving one-dimensional arrays, passing a one-dimensional array as a parameter is equivalent to passing a pointer to location 0 of the array, regardless of whether array notation or pointer notation is used. Thus, passing an array of pointers as a paremter is equivalent to passing a pointer to the pointer at location 0 of the array of pointers.
Although you have seen, many times, how the array of command-line arguments (a two-dimensional array of characters) is declared as a parameter, you have not yet seen it being passed as an argument. For an example of the latter, see arrayOfPointersDemo1.cpp. This program expects the user to enter a sequence of integers as command-line arguments. If all the command-line arguments (except argv[0], the filename of the executable file) represent integers, they are echoed to the terminal window. Otherwise, an error message is printed.
To complie this program, type:
g++ -s arrayOfPointersDemo1.cpp textUtility2.cppThis program defines a main function and a separate function parseArgvIntegers, which generates, as int values, the integers represented by the command-line arguments. The parseArgvIntegers function takes the array of command-line arguments as one of its parameters:
bool parseArgvIntegers(int argc, char* argv[], int numbers[])and is called in the main function as follows:
if ( ! parseArgvIntegers(argc, argv, inputNumbers) ) { cout << "You must enter integers only." << endl; return 1; } // ifThe parseArgvIntegers function applies parseForbinInt (a function defined in textUtility2.cpp) to all the elements of argv except argv[0].
The parseArgvIntegers function is intended specifically to process the array of command-line arguments, i.e. an array of pointers to C-strings, the first of which is reserved for the filename of the executable file. Thus, all but the first of the C-strings are read as integers.
Look now at arrayOfPointersDemo2.cpp, a program which behaves exactly like arrayOfPointers1.cpp. However, instead of the parseArgvIntegers function, arrayOfPointersDemo2.cpp contains a function parseIntegers which likewise reads C-strings as integers, but assumes that ALL of the C-strings in the array are intended to represent integers, rather than assuming that the C-string at location 0 is reserved for a separate purpose. The parseIntegers function reads all of the C-strings in an array integerTexts as integers and puts the resulting int values in numbers, which has the same length as numberTexts. Function parseIntegers has the following heading:
bool parseIntegers(char* integerTexts[], int numbers[], int length)where length is the length of both arrays.
Because the parseIntegers function does not assume that the C-string at location 0 of integerTexts is reserved for a separate purpose, we cannot simply pass the array of command-line arguments as an argument to this function. Instead, we need to pass to it a subarray of the array of command-line arguments, skipping argv. This is accomplished by the function call below:
if ( ! parseIntegers(argv + 1, inputNumbers, NUMBER_OF_NUMBERS) ) { cout << "You must enter integers only." << endl; return 1; } // ifusing pointer arithmetic to pass, as an argument, a subarray of the array of command-line arguments, beginning with argv[1].
- Matrices.
Our next example program will involve the mathematical entities known as matrices. Since some of you may not have seen matrices before, here is a brief introduction.A system of linear equations is a collection of equations involving a collection of variables, such as, for example:
x - 3y + 40z = 8 3x + 7y + 8z = -3 2x + 3y + 4z = 0where the only arithmetic operations are (1) multiplication (or division) of a variable by a constant known as the variable's coefficent and (2) addition (or substraction) of variable-coeffiecient products and other constants.
The coefficients of all the variables can be abstracted into a two-dimensional table known as a matrix. For example, the matrix of coefficients of the above system of linear equations would be:
-- -- | 1 -3 40 | | 3 7 8 | | 2 3 4 | -- --Matrices (the plural of matrix) can be treated as mathematical entities unto themselves, on which arithmetic operations can be performed. We will now consider matrix addition and matrix multiplication.
Matrix addition is simple. To add two matrices, simply add correcponding elements of the two matrices. For example:
-- -- -- -- -- -- | 1 2 | | 10 20 | | 11 22 | | 3 4 | + | 30 40 | = | 33 44 | | 5 6 | | 50 60 | | 55 66 | -- -- -- -- -- --In order for matrix addition to be possible, the two matrices to be added must have the same number of rows and columns as each other. The sum matrix also has the same number of rows and columns as each of the addend matrices.
Matrix addition is defined as follows: Let A nad B be a pair of matrices each with m rows and n columns. Let C be the sum matrix A + B. C will also have m rows and n columns, where the elements of C are defined as follows:
c = a + b i,j i,j i,j where a is the element in the ith row, jth column of matrix A i,j b is the element in the ith row, jth column of matrix B i,j c is the element in the ith row, jth column of matrix C i,jMatrix multiplication is more complicated. Below are both an example and a definition of matrix multiplcation. (You may need to look back and forth between the example and the definition several times to understand matrix multiplication fully.) First, the example:
-- -- -- -- | 1 0 2 | | 1 2 0 -1 | | 2 -1 0 | * | 0 1 -1 0 | -- -- | -1 0 1 2 | -- -- -- -- = | -1 2 2 3 | | 2 3 2 -2 | -- --which is computed as follows:
- Row 1, column 1: 1*1 + 0*0 + 2*(-1) = -1
- Row 1, column 2: 1*2 + 0*1 + 2*0 = 2
- Row 1, column 3: 1*0 + 0*(-1) + 2*1 = 2
- Row 1, column 4: 1*(-1) + 0*0 + 2*2 = 3
- Row 2, column 1: 2*1 + (-1)*0 + 0*(-1) = 2
- Row 2, column 2: 2*2 + (-1)*1 + 0*0 = 3
- Row 2, column 3: 2*0 + (-1)*(-1) + 0*1 = 2
- Row 2, column 4: 2*(-1) + (-1)*0 + 0*2 = -2
Matrix multiplication is defined as follws: Let A and B be a pair of matrices we are multiplying. In order for matrix multiplication to be possible, the number of columns in the first factor matrix, A, must equal the number of rows in the second factor matrix, B. With that in mind, let A be a matrix with m rows and n columns. Let B be a matrix with n rows and p columns. Let C be the product matrix A x B. C will have m rows and p columns, where the elements of C are defined as follows:
c = a * b + a * b + ... + a * b i,k i,1 1,k i,2 2,k i,n n,k where a is the element in the ith row, jth column of matrix A i,j b is the element in the jth row, kth column of matrix B j,k c is the element in the ith row, kth column of matrix C i,kAbove, note that the rows of a matrix are numbered 1 up to the number of rows, and that the columns of a matrix are numbered 1 up to the number of columns. This is standard C++ notation for arrays, which is different from C++ array indices, for which the rows of a matrix would be numbered 0 up to one less than the number of rows, and the columns of a matrix would be numbered 0 up to one less than the number of columns.
If you do not understand matrix multiplication, ask your instructor for clarification before you begin work on the relevant practice problem.
Note that matrix multiplication is NOT communtative. It matters which of the two factor matrices comes first.
- Two-dimensional arrays (the one-big-array kind) as parameters to functions.
Compile matrixAddition.cpp by typing:g++ -s matrixAddition.cpp textUtility2.cppand run it. As its name suggests, this program performs matrix addition. It reads two matrices from input text data file matricesToAdd.txt and prints the result matrix to output file matrixSum.txt.
The program defines a main function and three other functions: (1) readMatrices, which reads two matrices from a specified text file, (2) add, which adds the two matrices, and (3) printMatrix, which prints a matrix to the specified text file.
At the top of matrixAddition.cpp, the dimensions of all three matrices are declared as global constants:
const int ROWS = 3; const int COLUMNS = 4;Although global variables (with rare exceptions, such as cin and cout) are a no-no, global constants are OK. Because the value of a constant can't change, there is no difficulty in keeping track of changes to its value. Nevertheless the use of global constants -- like the use of any global variables -- should be documented in the comments above any function which uses them -- so that, if a programmer later decides to copy and paste the function into another file, the programmer doesn't have to spend time inspecting the body of the function to figure out what global constants it needs.
In order for matrix addition to be possible, the two addend matrices must have the same number of rows and columns; and the sum matrix will also have the same numbers of rows and columns as each of the two addend matrices. Hence we need only two constants to specify the dimensions of all three matrices.
Look now at the add function:
void add(const float addend1[][COLUMNS], const float addend2[][COLUMNS], float sum[][COLUMNS]) { for ( int i = 0; i < ROWS; i++ ) for ( int j = 0; j < COLUMNS; j++ ) sum[i][j] = addend1[i][j] + addend2[i][j]; } // function addWhen a 2-dimensional array of the one-big-array kind is declared as a parameter to a function, the inner dimension (COLUMNS, in this example) must be specified in the function heading, as shown above. More generally, when a multi-dimensional array of the one-big-array kind is declared as a parameter to a function, all dimensions except the outermost dimension must be specified.
The specified dimension must be one of the following: (1) an integer literal, (2) a global integer constant, or (3) an arithmetic expression involving integer literals, global integer constants, or both. It CANNOT be a variable.
Thus, when using the one-big-array style of two-dimensional array, you cannot write a function which will work for different sizes of arrays. If, in your program, you want to define a function which does the same thing for several different sizes of two-dimensional arrays, with different inner dimensions, then your file must contain several copies of the entire function definition, identical except for the inner dimension of the array.
This is one of the more annoying features of the one-big-array style of two-dimensional array, and is one of the reasons why the array-of-pointers style is more commonly used by programmers.
In the homework, you will be asked to write two very similar programs which do matrix multiplication instead of matrix addition. It is recommended that you specify a total of three global constants: (1) the number of rows in the first factor array, which will be the same as the number of rows in the product array; (2) the number of columns in the first factor array, which must be the same as the number of rows in the second factor array; and (3) the number of columns in the second factor array, which will be the same as the number of columns in the product array.
Look now at matrixAddition2.cpp, a program which behaves exactly like matrixAddition.cpp. The only difference is that matrixAddition2.cpp uses a typedef statement to create a special data type Matrix for 2-dimensional arrays of a particular size. Then, throughout the program, all the arrays -- both formal parameters and non-parameter variables -- are declared to be of type Matrix, instead of declaring them directly as 2-dimensional arrays of float. Observe that the typedef statement specifies both dimensions of the array.
In the homework, you will be asked to write two different versions of a matrix multiplication program, one using typedef and one not using typedef. In the version using typedef, you will need a total of three typedef statements, one for each of three sizes of arrays.
- More about two-dimensional arrays (the one-big-array kind) as parameters to functions.
Look now at oneBigArrayDemo1.cpp, a program which behaves exactly like arrayOfPointersDemo1.cpp and arrayOfPointersDemo2.cpp, except that it will complain about any command-line argument longer than 11 characters.Our latest example program oneBigArrayDemo1.cpp defines a function parseIntegers, which behaves exactly like the parseIntegers function in arrayOfPointersDemo2.cpp, except that the oneBigArrayDemo1.cpp version of the function takes, as its parameter array of C-strings, a two-dimensional array of the one-big-array style, rather than the array-of-pointers style.
In the main function, the command-line arguments (excluding argv[0]) are copied into argvCopy, a two-dimensional array of the one-big-array style, which is then passed as an argument to the parseIntegers function.
Each of the C-strings must be long enough to hold a string representation of any int value. The largest possible number of characters in such a C-string is 11, including the minus sign. We have declared a global constant to represent this maximum length:
const int MAX_INT_LENGTH = 11; // maximum number of characters // in string representing int, // including minus sign.The length of the array of characters holding a C-string must be one greater than the length of the longest actual C-string we expect to store, to allow one extra memory location for the null character. So, the inner dimension of our 2-dimensional array of characters is MAX_INT_LENGTH + 1.
Look now at oneBigArrayDemo2.cpp, a program exactly like oneBigArrayDemo1.cpp except that (1) ALL of the command-line arguments, including argv[0], are copied into argvCopy, and (2) a subarrray of argvCopy, excluding the copy of argv[0], is passed as an argument to the parseIntegers function. The subarray is obtained via pointer arithmetic.
if ( ! parseIntegers(argvCopy + 1, inputNumbers, NUMBER_OF_NUMBERS) )Recall that argvCopy is a two-dimensional array of the one-big-array style. Thus we can see that when we declare a two-dimensional array as a parameter to a function, it is equivalent to declaring a pointer. This is true for the one-big-array style of two-dimensional array, as well as for an array of pointers.
However, in the case of the one-big-array style of two-dimensional array, only the OUTER array is treated as a pointer. The inner arrays are NOT treated as pointers. Thus, a 2-dimensional array of the one-big-array style is NOT interchangeable with an array of pointer when declaring it as a parameter or passing it as an argument to a function.
In the one-big-array style of two-dimensional array, the size of the inner arrays determines the meaning of a pointer arithmetic expression for the outer array. For example, the expression argvCopy + 1 means the address of the beginning of argvCopy plus the length (in actual memory locations) of one inner array. The expression argvCopy + 2 would mean the address of the beginning of argvCopy plus the combined length of two inner arrays. And so on. That's why the inner dimension must be specified in a parameter declaration.
On the other hand, for an array of pointers, an expression like argv + 1 means the address of the beginning of argv plus the length of one pointer -- i.e., just one memory location containing the pointer itself -- NOT the amount of memory taken up by one inner array.
Thus, pointer arithmetic on the outer array has different meanings for the two kinds of two-dimensional arrays. When declared as parameters, the two kinds of two-dimensional arrays are treated as two distinct -- and incompatible -- types of pointers. When an array of pointers is passed as a parameter, the outer array is treated as a pointer to a pointer, meaning that any pointer arithmetic will be done in terms of the size of a memory location containing a pointer. On the other hand, a two-dimensional-array of the one-big-array style is literally an array of arrays -- i.e. a collection of the inner arrays themselves, physically next to each other in memory -- NOT an array of pointers to arrays. So, when a two-dimensional-array of the one-big-array style is declared as a parameter to a function, it is treated as a pointer to a region in memory the size of one inner array, meaning that any pointer arithmetic will be done in terms of memory blocks of that size.
This is why the two kinds of two-dimensional arrays are incompatible in terms of parameter-passing.
Back to: