Topic : Pointers and Arrays in C
Author : Ted Jensen
Page : << Previous 7  Next >>
Go to page :


  return 0;
}

----------------- end of program 6.1 ---------------------


Because of the double de-referencing required in the pointer version, the name of a 2 dimensional array is often said to be equivalent to a pointer to a pointer. With a three dimensional array we would be dealing with an array of arrays of arrays and some might say its name would be equivalent to a pointer to a pointer to a pointer. However, here we have initially set aside the block of memory for the array by defining it using array notation. Hence, we are dealing with a constant, not a variable. That is we are talking about a fixed address not a variable pointer. The dereferencing function used above permits us to access any element in the array of arrays without the need of changing the value of that address (the address of multi[0][0] as given by the symbol multi).

CHAPTER 7: More on Multi-Dimensional Arrays
In the previous chapter we noted that given

#define ROWS 5
#define COLS 10

int multi[ROWS][COLS];


we can access individual elements of the array multi using either:

multi[row][col]

or

*(*(multi + row) + col)

To understand more fully what is going on, let us replace

*(multi + row)

with X as in:

*(X + col)

Now, from this we see that X is like a pointer since the expression is de-referenced and we know that col is an integer. Here the arithmetic being used is of a special kind called "pointer arithmetic" is being used. That means that, since we are talking about an integer array, the address pointed to by (i.e. value of) X + col + 1 must be greater than the address X + col by and amount equal to sizeof(int).

Since we know the memory layout for 2 dimensional arrays, we can determine that in the expression multi + row as used above, multi + row + 1 must increase by value an amount equal to that needed to "point to" the next row, which in this case would be an amount equal to COLS * sizeof(int).

That says that if the expression *(*(multi + row) + col) is to be evaluated correctly at run time, the compiler must generate code which takes into consideration the value of COLS, i.e. the 2nd dimension. Because of the equivalence of the two forms of expression, this is true whether we are using the pointer expression as here or the array expression multi[row][col].

Thus, to evaluate either expression, a total of 5 values must be known:

The address of the first element of the array, which is returned by the expression multi, i.e., the name of the array.
The size of the type of the elements of the array, in this case sizeof(int).
The 2nd dimension of the array
The specific index value for the first dimension, row in this case.
The specific index value for the second dimension, col in this case.
Given all of that, consider the problem of designing a function to manipulate the element values of a previously declared array. For example, one which would set all the elements of the array multi to the value 1.

void set_value(int m_array[][COLS])
{
    int row, col;
    for (row = 0; row < ROWS; row++)
    {
        for (col = 0; col < COLS; col++)
        {
            m_array[row][col] = 1;
        }
    }
}


And to call this function we would then use:

set_value(multi);

Now, within the function we have used the values #defined by ROWS and COLS that set the limits on the for loops. But, these #defines are just constants as far as the compiler is concerned, i.e. there is nothing to connect them to the array size within the function. row and col are local variables, of course. The formal parameter definition permits the compiler to determine the characteristics associated with the pointer value that will be passed at run time. We really don’t need the first dimension and, as will be seen later, there are occasions where we would prefer not to define it within the parameter definition, out of habit or consistency, I have not used it here. But, the second dimension must be used as has been shown in the expression for the parameter. The reason is that we need this in the evaluation of m_array[row][col] as has been described. While the parameter defines the data type (int in this case) and the automatic variables for row and column are defined in the for loops, only one value can be passed using a single parameter. In this case, that is the value of multi as noted in the call statement, i.e. the address of the first element, often referred to as a pointer to the array. Thus, the only way we have of informing the compiler of the 2nd dimension is by explicitly including it in the parameter definition.

In fact, in general all dimensions of higher order than one are needed when dealing with multi-dimensional arrays. That is if we are talking about 3 dimensional arrays, the 2nd and 3rd dimension must be specified in the parameter definition.

CHAPTER 8: Pointers to Arrays
Pointers, of course, can be "pointed at" any type of data object, including arrays. While that was evident when we discussed program 3.1, it is important to expand on how we do this when it comes to multi-dimensional arrays.

To review, in Chapter 2 we stated that given an array of integers we could point an integer pointer at that array using:

int *ptr;
ptr = &my_array[0];       /* point our pointer at the first
                                 integer in our array */


As we stated there, the type of the pointer variable must match the type of the first element of the array.

In addition, we can use a pointer as a formal parameter of a function which is designed to manipulate an array. e.g.

Given:

int array[3] = {'1', '5', '7'};
void a_func(int *p);


Some programmers might prefer to write the function prototype as:

void a_func(int p[]);

which would tend to inform others who might use this function that the function is designed to manipulate the elements of an array. Of course, in either case, what actually gets passed is the value of a pointer to the first element of the array, independent of which notation is used in the function prototype or definition. Note that if the array notation is used, there is no need to pass the actual dimension of the array since we are not passing the whole array, only the address to the first element.

We now turn to the problem of the 2 dimensional array. As stated in the last chapter, C interprets a 2 dimensional array as an array of one dimensional arrays. That being the case, the first element of a 2 dimensional array of integers is a one dimensional array of integers. And a pointer to a two dimensional array of integers must be a pointer to that data type. One way of accomplishing this is through the use of the keyword "typedef". typedef assigns a new name to a specified data type. For example:

typedef unsigned char byte;

causes the name byte to mean type unsigned char. Hence

byte b[10];

would be an array of unsigned characters.

Note that in the typedef declaration, the word byte has replaced that which would normally be the name of our unsigned char. That is, the rule for using typedef is that the new name for the data type is the name used in the definition of the data type. Thus in:

typedef int Array[10];

Array becomes a data type for an array of 10 integers. i.e. Array my_arr; declares my_arr as an array of 10 integers and Array arr2d[5]; makes arr2d an array of 5 arrays of 10 integers each.

Also note that Array *p1d; makes p1d a pointer to an array of 10 integers. Because *p1d points to the same type as arr2d, assigning the address of the two dimensional array arr2d to p1d, the pointer to a one dimensional array of 10 integers is acceptable. i.e. p1d = &arr2d[0]; or p1d = arr2d; are both correct.

Since the data type we use for our pointer is an array of 10 integers we would expect that incrementing p1d by 1 would change its value by 10*sizeof(int), which it does. That is, sizeof(*p1d) is 20. You can prove this to yourself by writing and running a simple short program.

Now, while using typedef makes things clearer for the

Page : << Previous 7  Next >>