Previous Table of Contents Next


An external declaration does not necessarily refer to a function or variable that is defined in another source file; it merely refers to a function or variable that is defined somewhere else, perhaps elsewhere in the same source file. When multiple interrelated functions are placed in the same source file, two broad arrangement strategies are possible:

  “Top-down”: A source file begins with external declarations for all functions called within the source file. Then come the functions themselves, arranged in the same order as the function-call hierarchy: top-level functions first, followed by the functions they call. The external declarations at the top of the file are necessary so that declarations are in scope by the time the calls within the top-level function appear; at least some of those calls will presumably be to lower-level functions for which definitions have not been seen yet. The external declarations at the top of the file do not necessarily refer to functions defined in other source file(s); some or all are of functions that are defined later in the same source file.
  “Bottom-up”: The functions in a source file are arranged in the reverse order of the function-call hierarchy: lowest-level functions first, followed by the higher-level functions that call them. Fewer external function declarations are required, because by the time a higher-level function appears, actual definitions may already have been seen for some or all of the lower-level functions that are to be called.

In either case, external declarations may still be required for called functions that are defined in other source files. External declarations may also be required when the function-call hierarchy is not strict (e.g., if it contains cycles). Finally, although we have spoken of the external declarations as appearing within the source file, they are often placed in header files, in which case all that appears in the main source file is the #include directive, which references the header file. (See section 3.8.1.)

3.6. Pointers and Arrays

Pointers are C’s reference types. Besides providing extra levels of indirection, they are also indispensable in dynamic memory allocation and, when portability is not a concern, in accessing memory or other hardware at a very low level.

Conceptually, at a high level, we say that a pointer “points to” some other object or function. Pointers are themselves objects, and like all objects, they have types: The type of a pointer defines the types of objects (or functions) to which it can point and that result when the pointer is indirected upon (that is, when the value pointed to by the pointer is accessed). At a lower level, it is possible to think of pointers as machine addresses (with which, in fact, they are almost invariably implemented).

Additionally, there is one “generic” object pointer type that may be used as a container for object pointers of unspecified type, and there is a special null pointer value (or, more precisely, potentially one null pointer value for each pointer type) that points reliably nowhere, and that will never compare equal to a pointer to any actual object or function.

3.6.1. Pointer Declarations

Pointers are declared by using asterisks in the declarator. For example, a simple pointer declaration is

int *ip;

This declares the variable ip as a pointer, of type “pointer to int.” One way of interpreting this declaration is that, as we’ll see, when the indirection or “contents of” operator is applied to the pointer variable ip, the expression *ip will refer to an int.

Because the asterisk indicating a pointer is part of the declarator, not of the base type, it must be repeated if several pointer variables are being declared at once. That is, the correct way to declare two pointer variables in the same declaration is with a line like

int *ip1, *ip2;

This declaration can be contrasted with the line

int *p, q;

which declares p as type pointer-to-int and q as type int.

When type qualifiers are used in pointer declarations, their placement is significant. The declaration

const int *ptrtoconst;

declares ptrtoconst as type “pointer to const int.” The declaration

int * const constptr;

on the other hand, declares constptr as type “const pointer to int.” (These declarations have to be read “inside out” to make sense.) It is permissible to modify the int to which constptr points (that is, *constptr), and it is permissible to modify ptrtoconst to make it point to other const ints, but it is not permissible to modify constptr (the pointer itself) or *ptrtoconst (the pointed-to value). Pointers to constant values (e.g., const char *) are often used as function arguments, documenting (and in most cases ensuring) that the function does not use a particular pointer to modify any data in the caller. (See section 3.10 for many examples.)

The process by which pointer types are derived from other types is general, and it is quite possible to construct pointers to pointers (or pointers to pointers to pointers, and so on). For example, a pointer-to-pointer-to-int might be declared with

int **ipp;

It is important to recognize that a pointer variable (like any variable) cannot be used until it is initialized—that is, until some pointer value is assigned to it. The integer pointer variable ip, for example, although it has the capacity to point at integer values elsewhere in memory, does not, upon first being declared by the preceding declaration, point to any actual int-sized memory location yet. To use a pointer variable before it is initialized is as incorrect as to use any other variable before it is initialized.


Previous Table of Contents Next