Previous Table of Contents Next


3.6.4. Array/Pointer Initialization

Like all other variables, pointer variables can be initialized in their declarations. For example, we might write

int *ip = &i;

or, simplifying an example in the preceding section still further, we might write

char *cp = (char *)&i;

In these declarations, it is important to note that the initializer is assigned to the variable being declared, not to the expression-like construct that forms the declarator. That is,

int *ip = &i;

does not, as it might superficially appear, correspond to

int *ip;
*ip = &i;          /* WRONG */

but rather to

int *ip;
ip = &i;

Stated another way, the * in the declarator is not an actual pointer indirection operator.

When a pointer variable is global (or, more precisely, has static duration), its initializer must (as always) be a constant expression, as discussed in sections 3.2.8 and 3.3.12. The pointer-to operator & is permissible in constant expressions, with certain restrictions. It must be applied to an object that also has static duration, and the only pointer arithmetic allowed is addition or subtraction of a constant. For example, initializations such as

int global_i;
int *global_ip = &global_i;

and

int global_a[10];
int *global_ip2 = &global_a[0] + 2;

are legal.

3.6.5. Array/Pointer Equivalence

Perhaps the most significant innovation in C, and a feature that distinguishes it from most other languages, is its integration of arrays and pointers. Arrays and pointers do retain distinct identities under this “equivalence,” but it is particularly easy to use pointers to access arrays or to simulate arrays.

The cornerstone of the array/pointer equivalence in C is that whenever an array appears in an expression such that its value would seem to be needed, a pointer to the array’s first element is automatically generated instead. That is, if we have an array

int a[10];

and if we use it in an expression

a + 5

the effect is as if we had written

&a[0] + 5

This automatic generation of a pointer to an array’s first element might seem strange, but its utility becomes obvious when we consider the second facet of array/pointer equivalence, namely that the subscripting operator [] is actually defined for pointers. Given a pointer p and an integral expression e, the construction

p[e]

is defined as

*((p) + (e))

To understand the implication of this definition, consider the array a. Successive elements of the array are accessed with expressions like a[0], a[1], a[2], or a[i]. Having learned about pointer arithmetic in section 3.6.3, we might also access the array using a pointer, by declaring

int *ip;
ip = &a[0];

and then referring to *ip, *(ip+1), *(ip+2), or *(ip+i). But we can also simply assign

ip = a;

and refer to ip[0], ip[1], ip[2], or ip[i]. By definition, the assignment ip = a is equivalent to ip = &a[0], and the expression ip[i] is equivalent to *(ip+i). So having assigned ip to point to the beginning of the array, the expression ip[e] accesses the same element as a[e]. The resulting duality between arrays and pointers makes a certain amount of sense: An array, by definition, is a set of adjacent identical elements that can therefore be accessed by index, and pointer arithmetic is defined so as to access adjacent identical elements by adding (or subtracting) integer offsets. Because pointers and arrays access the same kind of data structure (a set of identical adjacent elements) in a conceptually similar way, it is reasonable for them to be able to use the same syntax—the subscripting brackets []—to do so.

Although arrays and pointers are both used to access the same kind of data structures, this does not mean that they are the same kind of data structures. An array is (by definition) a set of identical adjacent elements; a pointer is a different data type that is merely useful for accessing identical adjacent elements. In fact, as should be obvious from the fact that they are sometimes referred to as “reference” types, pointers always refer to data that is stored elsewhere. It is vital to keep in mind the distinction between the pointer itself and the data value it points to. One way of reinforcing this distinction is to consider figures like this one, representing the situation after the assignment ip = a:

We have mentioned that C does not support assignment of entire arrays at once; the code

int a[10], b[10];
b = a;          /* WRONG */

is illegal. But pointer assignment can be used to simulate array assignment, as follows:

int a[10], *b;
b = a;

Moreover, such an assignment is likely to be quite efficient because the array contents are not copied. (Of course, since they are not copied, any change to the array pointed to by b is, in fact, a change to the array a.)

Because strings in C are represented as arrays of characters, pointers of type char * find widespread use in manipulating strings. (In fact, since attempts to manipulate an array almost always result in a pointer to the array’s first element, it turns out that strings are almost always manipulated by pointers.) We see several examples of doing so in section 3.10.2. The connection between pointers to char and strings is so strong that it is common to use expressions like “the string s” or “the string pointed to by s” when the variable s is, strictly speaking, a pointer to the first character of the string.

The equivalence between arrays and pointers has several implications for function calls. If we call a function and attempt to pass an array to it, as in

f(a);

the array appears in a context in which its value is needed, so the effect is as if we had written

f(&a[0]);

The function f does not receive an entire copy of the array a; all it receives is a reference to the array, in the form of a pointer to the array’s first element.

How, then, should f’s parameter be declared? Strictly speaking, it seems as if f should receive a pointer, as if the definition should look like

f(int *x) { … }


Previous Table of Contents Next