[ Team LiB ] Previous Section Next Section

Gotcha #34: Pointer-to-Multidimensional-Array Problems

C and C++ arrays are pretty minimal. In fact, an array name is really not much more than a pointer literal that refers to the first element of the array:



int a[5]; 


int * const pa = a;


int * const *ppa = &pa;


const int alen = sizeof(a)/sizeof(a[0]); // alen == 5


The only practical differences between an array name and a constant pointer are that the array name gives the array size in a sizeof expression rather than giving the size of a pointer, and an array name occupies no storage and therefore has no address. To be clear: an array has an address, and that address is indicated by the array name; the array name itself has no address:



int *ip = a; // a is a ptr to first element of array 


int (*ap)[5] = &a; // &a is the address of the array, not a


int (*ap2)[sizeof(a)/sizeof(a[0])] = &a; // same thing


int **pip = &ip; // &ip is the address of a pointer, not an array


This is also the case for multidimensional arrays or, more properly, arrays of arrays. But remember that the type of the first element of a multidimensional array is an array, not the base type:



int aa[2][3]; 


const int aalen = sizeof(aa)/sizeof(aa[0]); // aalen = 2


Therefore, aa is essentially a pointer literal to the first element of an array of three integers. It's not a pointer to an integer. This can lead to some surprising, if technically correct, results:



void processElems( int *, size_t ); 


void processElems( void *, size_t );


// . . .


processElems( a, alen );


processElems( aa, aalen ); // oops!


The first call to the overloaded processElems function matches the version that takes an int * argument; the array name a is just an int * in disguise. The second call matches the version of processElem that takes a void *, which is probably not what the programmer intended. The type of the multidimensional array name is a pointer to its first element, which is an array of a particular size, not a pointer to the base type of the array. There is no implicit conversion of an int(*)[3] (that is, a pointer to an array of three integers) to an int *, but there is such a conversion to a void *.



int (* const paa)[3] = aa; 


int (* const *ppaa)[3] = &paa;


void processElems( int (*)[3], size_t );


// . . .


processElems( aa, aalen ); // OK.


Multidimensional arrays are problematic. A better alternative is generally to use the standard library containers or special-purpose containers that implement abstract multidimensional arrays. If a situation does require the use of raw multidimensional arrays, encapsulating them is usually best. It's just not responsible to expose a naive user of your interface to



int *(*(*aryCallback)(int *(*)[n]))[n]; 


This is (of course) a pointer to a function that takes a pointer to an array of n pointers to int and returns a pointer of the same type. All right, that's just showing off. (See Gotcha #11.) A typedef would have simplified things considerably:



typedef int *(*PA)[n]; 


PA (*aryCallback)(PA); // more humane


    [ Team LiB ] Previous Section Next Section