You are here

  1. Home
  2. » Experiments in Cosmology

The Golden Rule of Pointers

Submitted by cwestin on

Pointer declarations are among the most maligned parts of the C and C++ languages. Complex pointer declarations are considered hard to create and understand. Given the importance of using pointers in any application that manages dynamic resources, it is important to know one simple rule that makes understanding pointers easy.

Back to the Source

The difficulties many have with understanding pointers stem from ignorance of the details of the C language definition. The first edition of K&R, Appendix A, Section 8.4, Meaning of Declarators
states:
"Each declarator is taken to be an assertion that when a construction of the same form as the declarator appears in an expression, it yields an object of the indicated type and storage class."

The use of the word expression is only in reference to the identifier declared, not to the type or storage class. In short, when you use the declared identifier in an expression like the one it is being declared with, you get an object of the type specified in that declaration.

To apply this idea, when you see a declaration like

    T *D;
you should think "*D is a T." I call this The Golden Rule of Pointers. This is quite different from "D is a pointer to a T." The original K&R describes C declaration syntax as an assertion that the expression on the right has the type on the left. Note that moving the star (asterisk) to the left doesn't change what is considered the expression; the star is not part of the type. C (C++) is blind to whitespace, and wishfully putting the star on the left in an attempt to create a pointer type does not make the star a part of the type.

This description of declarations is the definition of declarations in C; ignore it at your own peril.

The Pitfalls of Ignoring K&R

The incorrect conceptualization "D is a pointer to a T" has led to many misunderstandings. It is the source of the incorrect pointer declaration style that groups the star (asterisk) with the typename in an attempt to create a declaration of a pointer:

    T* D; /* incorrect conceptualization */
This code was written under the belief that the star is part of the type, not part of the expression that has that type. Once programmers start using this style, they often fall prey to missing stars:
    T* D, D1; /* the incorrect conceptualization led to an error */

The question is, why is D1 not what I intended it to be, namely, a pointer to a T? The answer is that the syntax is not designed to work that way. The proper declaration is an assertion that the expressions on the right are of the type on the left:

    T *D, *D1;
This subtle distinction in interpretation makes it possible to create and interpret quite complex pointer declarations, as we will see below.

Note that these declarations say nothing at all about the nature of D or D1. These declarations only tell us what we get if we apply the * operator to D or D1.

Interpreting Complex Declarations

To make sense out of any declaration, start by evaluating the expression, then work backwards to get the common (spoken language) description using pointers.

Consider the following declaration:

    int *(*pfrpi)(int);
Here, the leftmost parentheses are used to bypass default precedence rules. We'll work our way from the inside out to interpret this declaration. pfrpi is something which we can dereference, then invoke as a function: the right most parentheses represent the function invocation operator, taking an integer argument. (Function invocation on the right takes precedence over the leftmost dereference.) Finally, the result of all that can be dereferenced to yield an (int). In more familiar terms, pfrpi is a pointer to a function (with an integer argument) that returns a pointer to an int.

Here is another example:

    int *(*pa10pi)[10];
If we start with pa10pi, we can dereference it, use it to index up to 10 elements, dereference any one of those, and get an (int). Therefore, it is a pointer to an array of 10 pointers to integers.
Type Qualifiers

The syntactic placement of type qualifiers can be confusing. Here are some examples demonstrating how the golden rule helps interpret the use of type qualifiers such as const and volatile. Consider the following declarations:

    const int *pci;
    int *const cpi;
    const int *const cpci;
In the first declaration, *pci is a (const int). In practical terms, pci is a pointer to a constant integer. Note that the pointer is not constant, but the integer it points to is; the compiler will not permit *pci to be modified, although pci may be modified.

The second declaration is an assertion that *cpi is an (int). *cpi can be modified, but cpi cannot. In this instance, we have a nested assertion in the declaration: "const cpi." As noted above, we have no idea of the nature of what cpi is; all we know is that we can't modify it.

The third declaration combines the features of the other two, asserting "const cpci," and "const int
*cpci
," declaring a constant pointer to a constant integer. Neither cpci nor *cpci may be modified.

C++ References are Broken

Reference declarations in C++ no longer follow the key to the declarator design scheme of K&R: in C++, using the declared reference variable in an expression identical to the one it was declared with yields a different type than the one declared. To see how this is so, compare pointer and reference use:

void f(int *px) /* I declare a pointer like this... */
{
     *px = 5; /* and I use it the same way. */
}

void g(int &x) /* I declare a reference like this... */
{
    x = 5; /* but I use it differently.  I don't use "&x = 5;" which means something else entirely */
}
In a non-declaration, the ampersand already has a well-known meaning: to obtain the address of its operand. Now, in a declaration, an ampersand indicates a level of indirection that should be invisibly applied in a non-declaration. Having a different meaning in two different contexts makes using ampersands confusing. This is the only case where a declared object cannot (and should not) be used in the same way it was declared.

Tags: