First of all, remember that C treats arrays very differently from Java. A declaration like
char foo[10];
allocates enough storage for 10 char
values and nothing else (modulo any additional space to satisfy alignment requirements); no additional storage is set aside for a pointer to the first element or any other kind of metadata such as array size or element class type. There's no object foo
apart from the array elements themselves1. Instead, there's a rule in the language that anytime the compiler sees an array expression that isn't the operand of the sizeof
or unary &
operator (or a string literal used to initialize another array in a declaration), it implicitly converts that expression from type "N-element array of T
" to "pointer to T
", and the value of the expression is the address of the first element of the array.
This has several implications. First is that when you pass an array expression as an argument to a function, what the function actually receives is a pointer value:
char foo[10];
do_something_with( foo );
...
void do_something_with( char *p )
{
...
}
The formal parameter p
corresponding to the actual parameter foo
is a pointer to char
, not an array of char
. To make things confusing, C allows do_something_with
to be declared as
void do_something_with( char p[] )
or even
void do_something_with( char p[10] )
but in the case of function parameter declarations, T p[]
and T p[N]
are identical to T *p
, and all three declare p
as a pointer, not an array2. Note that this is only true for function parameter declarations.
The second implication is that the subscript operator []
can be used on pointer operands as well as array operands, such as
char foo[10];
char *p = foo;
...
p[i] = 'A'; // equivalent to foo[i] = 'A';
The final implication leads to one case of dealing with pointers to pointers - suppose you have an array of pointers like
const char *strs[] = { "foo", "bar", "bletch", "blurga", NULL };
strs
is a 5-element array of const char *
3; however, if you pass it to a function like
do_something_with( strs );
then what the function receives is actually a pointer to a pointer, not an array of pointers:
void do_something_with( const char **strs ) { ... }
Pointers to pointers (and higher levels of indirection) also show up in the following situations:
- Writing to a parameter of pointer type: Remember that C passes all parameters by value; the formal parameter in the function definition is a different object in memory than the actual parameter in the function call, so if you want the function to update the value of the actual parameter, you must pass a pointer to that parameter:
void foo( T *param ) // for any type T
{
*param = new_value(); // update the object param *points to*
}
void bar( void )
{
T x;
foo( &x ); // update the value in x
}
Now suppose we replace the type T
with the pointer type R *
, then our code snippet looks like this:
void foo( R **param ) // for any type R *
{
...
*param = new_value(); // update the object param *points to*
...
}
void bar( void )
{
R *x;
foo( &x ); // update the value in x
}
Same semantics - we're updating the value contained in x
. It's just that in this case, x
already has a pointer type, so we must pass a pointer to the pointer. This can be extended to higher levels of direction:
void foo( Q ****param ) // for any type Q ***
{
...
*param = new_value(); // update the object param *points to*
...
}
void bar( void )
{
Q ***x;
foo( &x ); // update the value in x
}
- Dynamically-allocated multi-dimensional arrays: One common technique for allocating multi-dimensional arrays in C is to allocate an array of pointers, and for each element of that array allocate a buffer that the pointer points to:
T **arr;
arr = malloc( rows * sizeof *arr ); // arr has type T **, *arr has type T *
if ( arr )
{
for ( size_t i = 0; i < rows; i++ )
{
arr[i] = malloc( cols * sizeof *arr[i] ); // arr[i] has type T *
if ( arr[i] )
{
for ( size_t j = 0; j < cols; j++ )
{
arr[i][j] = some_initial_value();
}
}
}
}
This can be extended to higher levels of indirection, so you have have types like T ***
and T ****
, etc.
1. This is part of why array expressions may not be the target of an assignment; there's nothing to assign anything to.
This is a holdover from the B programming language from which C was derived; in B, a pointer is declared as auto p[]
.
Each string literal is an array of char
, but because we are not using them to initialize individual arrays of char
, the expressions are converted to pointer values.