Previous Table of Contents Next


However, to preserve the illusion that it is possible to define functions that operate on arrays, it is also permissible to write

f(int x[])
{ … }

The compiler knows that f does not actually receive an array as a parameter, so it quietly rewrites the declaration of the parameter x to type “pointer to int.” This rewriting rule also explains why the size is optional (and usually omitted) in one-dimensional array parameters: Because the parameter is actually a reference to an array declared elsewhere, the size is immaterial, as the actual size was also set elsewhere.

Although a function like f does, in fact, receive a pointer, the pointer can be accessed within the function as if it were an array because the pointer is to the first element of the passed array and because (once again) the subscripting brackets do work with pointers.

The “conversion” of arrays to pointers during function calls occurs only for the first dimension of the array. If we have, for example, a two-dimensional array, such as

int a2[5][7];

which we pass to a function with a call like f2(a2), the compiler again passes a function to the array’s first element, still as if we had written f2(&a2). In this case, however, the first element is itself an array, so the pointer actually passed is of type “pointer to array of 7 ints.” The function can be defined either as having a parameter explicitly declared of that type

f2(int (*x)[7])
{ … }

(see section 3.6.7 below for a discussion of the bewildering-looking declarator (*x)[7]), or as a seemingly two-dimensional array type

f2(int x[5][7])
{ … }

In the second definition, the compiler performs the corresponding reinterpretation, back to “pointer to array of 7 ints” (in this case, ignoring the unneeded size 5). Notice, however, that the function

f2a(int **x)
{ … }

is not equivalent, and it would not be correct to pass the two-dimensional array a2 to this function.

As a point of style, it is debatable whether the compiler’s array parameter rewriting rules are worth using or not. Using them (that is, defining functions that look as if they accept arrays) has the advantage that the declarations look like the types of the actual arguments presumably being passed, and they are also somewhat easier to type and read in the case of multidimensional arrays. However, these rewriting rules are also responsible for a certain amount of confusion, and some programmers therefore prefer to “call a spade a spade” and declare array-like function parameters explicitly as the pointers they truly are.

Finally, you might ask what the right name for the subscripting operator [] is if it can be used with both arrays and pointers. In fact, strictly speaking, it is always used with pointers; even in a trivial expression like a[0], a strict application of the pointer equivalence rule says that a pointer to a’s first element is first generated, such that the subscript [0] is actually applied to that pointer. But this is a surprising result, and it is perfectly acceptable to refer to [] as the “array subscripting operator,” as long as we remember that it can also be applied to pointers.

3.6.6. Memory Allocation

One of the principal uses for pointers in C is dynamic memory allocation. The C compiler supports only the allocation of objects whose sizes are known at compile time. It is impossible to declare a true array whose size is a runtime expression. But because pointers can be used to access blocks of adjacent memory locations as conveniently as can arrays, they can be used to simulate dynamically sized arrays.

The key to doing so is the standard library function malloc, which returns a pointer to a new, contiguous area of memory having a requested size. (malloc is declared in the standard header <stdlib.h>; the line #include <stdlib.h> should be assumed to be present before all code fragments in this section.) For example, to obtain a new block of memory capable of holding n characters (and therefore capable of holding a string of length up to n-1), the first step is to declare a pointer and then allocate memory for it by calling malloc:

char *p;
p = malloc(n);

malloc’s single argument is the number of bytes requested. If malloc is able to satisfy the request, it returns a pointer to a block of new memory of (at least) the requested size. Otherwise, it returns a null pointer. (Recall that the null pointer, by definition, does not compare equal with a pointer to any object and therefore points “nowhere.”) Whenever you’re calling malloc, therefore, it is important to check for this null pointer return before using the pointer:

p = malloc(n);
if(p == NULL)
       { printf(“out of memory\n”);
       return;
       }
/* …otherwise use pointer p */

(Of course, in production code, it might be necessary to return an error code to the caller in the out-of-memory case, or continue processing in some different way, or perhaps to abort the program.)

Having initialized the pointer p in this way, it can be treated almost as if it were an array of size n. In particular, due to the equivalence between arrays and pointers, the rest of the program can refer to p[i] (for any index expression i), making the illusion that p is an array a very convincing one indeed.

Suppose it is necessary to dynamically allocate some number of integers, perhaps to simulate an array of int. How many bytes should be requested from malloc? You could conceivably determine how big (in bytes) one integer happens to be on the current machine and then perform the appropriate scaling before calling malloc, but doing so would needlessly tie the program to that one machine. Instead, C provides the sizeof operator, which computes the sizes of objects and allows memory-management code to be written much more portably. For example, to allocate n integers, you can call

int *ip = malloc(n * sizeof(int));

The sizeof operator takes one operand—in this case, the parenthesized type name int—and returns the size of that operand (on the current machine) in bytes. Notice that, despite its appearance, sizeof is not a function but rather an operator that does its work at compile time.


Previous Table of Contents Next