Array Formal Arguments





Item 6. Array Formal Arguments

Array formal arguments are problematic. The major surprise in store for the C/C++ novice is that there are no array formal arguments, because an array is passed as a pointer to its first element.

void average( int ary[12] ); // formal arg is int *
//...
int anArray[] = { 1, 2, 3 }; // three-element array
const int anArraySize =
    sizeof(anArray)/sizeof(anArray[0]); // == 3
average( anArray ); // legal!

This automatic transformation from array to pointer is given the charming technical term "decay"; an array decays to a pointer to its first element. The same thing happens to functions, by the way. A function argument decays to a pointer to a function, but, unlike an array that loses its bound, a decaying function has the good sense to hold onto its argument and return types. (Note also the proper compile-time calculation of anArraySize that can withstand changes both to the set of initializers of the array and to the array's element type.)

Because the array bound is ignored in an array formal argument, it's usually best to omit it. However, if the function is expecting a pointer to a sequence of elements (that is, an array) as an argument, rather than a pointer to a single object, then it's probably best to say so:

void average( int ary[] ); // formal arg is still int *

If, on the other hand, the precise value of the array bound is important, and you want the function to accept only arrays with a particular number of elements, you may consider a reference formal argument:

void average( int (&ary)[12] );

Now our function will accept only integer arrays of size 12.

average( anArray ); // error! anArray is int [3]!

Templates can help to generalize somewhat,

template <int n>
void average( int (&ary)[n] ); // let compiler deduce n for us

but a more traditional solution is to pass the size of the array explicitly.

void average_n( int ary[], int size );

Of course, we can combine the two approaches:

template <int n>
inline void average( int (&ary)[n] )
    { average_n( ary, n ); }

It should be clear from this discussion that one of the major problems with using arrays as function arguments is that the size of the array must be encoded explicitly in the type of the formal argument, passed as a separate argument, or indicated by a "terminator" value within the array itself (such as the '\0' that is used to indicate the end of a character array used as a string). Another difficulty is thatno matter how it is declaredan array is often manipulated through a pointer to its first element. If that pointer is passed as the actual argument to a function, our previous trick of declaring a reference formal argument will not help us.

int *anArray2 = new int[anArraySize];
//...
average( anArray2 ); // error! can't init int(&)[n] with int *
average_n( anArray, anArraySize ); // OK...

For these reasons, one of the standard containers (typically vector or string) is often a good substitute for most traditional uses of arrays and should generally be considered first. See also the Array class template in You Instantiate What You Use [61, 225].

Multidimensional array formal arguments are not essentially more difficult than simple arrays, but they look more challenging:

void process( int ary[10][20] );

As in the single-dimensional case, the formal argument is not an array but a pointer to the array's first element. However, a multidimensional array is an array of arrays, so the formal argument is a pointer to an array (see Dealing with Function and Array Declarators [17, 61] and Pointer Arithmetic [44, 149]).

void process( int (*ary)[20] ); // ptr to array of 20 ints

Note that the second (and subsequent) bounds are not decayed, because otherwise it would not be possible to perform pointer arithmetic with the formal argument (see Pointer Arithmetic [44, 149]). As noted previously, it's often a good idea to let your reader know you expect the actual argument to be an array:

void process( int ary[][20] ); // still a pointer, but clearer

Effective processing of multidimensional array arguments often decays into an exercise in low-level coding, with the programmer taking the compiler's place in performing index calculations:

void process_2d( int *a, int n, int m ) { // a is an n by m array
    for( int i = 0; i < n; ++i )
        for( int j = 0; j < m; ++j )
            a[i*m+j] = 0; // calculate index "by hand"!
}

As usual, a template can sometimes help to clean things up.

template <int n, int m>
inline void process( int (&ary)[n][m] )
    { process_2d( &ary[0][0], n, m ); }

Simply put, array formal arguments are a pain in the neck. Approach with caution.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows