5.7. Expressions involving pointers

Because of the introduction of qualified types and of the notion of incomplete types, together with the use of void *, there are now some complicated rules about how you can mix pointers and what arithmetic with pointers really permits you to do. Most people will survive quite well without ever learning this explicitly, because a lot of it is ‘obvious’, but we will include it here in case you do want to know. For the final word in accuracy, obviously you will want to see what the Standard says. What follows is our interpretation in (hopefully) plainer English.

You don't yet know the Standard means when it talks about objects or incomplete types. So far we have tended to use the term loosely, but properly speaking an object is a piece of data storage whose contents is to be interpreted as a value. A function is not an object. An incomplete type is one whose name and type are mostly known, but whose size hasn't yet been determined. You can get these in two ways:

  1. By declaring an array but omitting information about its size: int x[];. In that case, there must be additional information given later in a definition for the array. The type remains incomplete until the later definition.
  2. By declaring a structure or union but not defining its contents. The contents must be defined in a later declaration. The type remains incomplete until the later declaration.

There will be some more discussion of incomplete types in later chapters.

Now for what you are allowed to do with pointers. Note that wherever we talk about qualified types they can be qualified with const, volatile, or both; the examples are illustrated with const only.

5.7.1. Conversions

Pointers to void can be freely converted backwards and forwards with pointers to any object or incomplete type. Converting a pointer to an object or an incomplete type to void * and then back gives a value which is equal to the original one:

int i;
int *ip;
void *vp;

ip = &i;
vp = ip;
ip = vp;
if(ip != &i)
      printf("Compiler error\n");

An unqualified pointer type may be converted to a qualified pointer type, but the reverse is not true. The two values will be equal:

int i;
int *ip;
const int *cpi;

ip = &i;
cpi = ip;       /* permitted */
if(cpi != ip)
      printf("Compiler error\n");
ip = cpi;       /* not permitted */

A null pointer constant (see earlier) will not be equal to a pointer to any object or function.

5.7.2. Arithmetic

Expressions can add (or subtract, which is equivalent to adding negative values) integral values to the value of a pointer to any object type. The result has the type of the pointer and if n is added, then the result points n array elements away from the pointer. The most common use is repeatedly to add 1 to a pointer to step it from the start to the end of an array, but addition or subtraction of values other than one is possible.

It the pointer resulting from the addition points in front of the array or past the non-existent element just after the last element of the array, then you have had overflow or underflow and the result is undefined.

The last-plus-one element of an array has always been assumed to be a valid address for a pointer and the Standard confirms this. You mustn't actually access that element, but the address is guaranteed to exist rather than being an overflow condition.

We've been careful to use the term ‘expression’ rather than saying that you actually add something to the pointer itself. You can do that, but only if the pointer is not qualified with const (of course). The increment and decrement operators are equivalent to adding or subtracting 1.

Two pointers to compatible types whether or not qualified may be subtracted. The result has the type ptrdiff_t, which is defined in the header file <stddef.h>. Both pointers must point into the same array, or one past the end of the array, otherwise the behaviour is undefined. The value of the result is the number of array elements that separate the two pointers. E.g.:

int x[100];
int *pi, *cpi = &x[99]; /* cpi points to the last element of x */

pi = x;
if((cpi - pi) != 99)
      printf("Error\n");

pi = cpi;
pi++;                   /* increment past end of x */
if((pi - cpi) != 1)
      printf("Error\n");

5.7.3. Relational expressions

These allow us to compare pointers with each other. You can only compare

It does not matter if the types that are pointed to are qualified or unqualified.

If two pointers compare equal to each other then they point to the same thing, whether it is an object or the non-existent element off the end of an array (see arithmetic, above). If two pointers point to the same thing, then they compare equal to each other. The relational operators >, <= and so on all give the result that you would expect if the pointers point into the same array: if one pointer compares less than another, then it points nearer to the front of the array.

A null pointer constant can be assigned to a pointer; that pointer will then compare equal to the null pointer constant (which is pretty obvious). A null pointer constant or a null pointer will not compare equal to a pointer that points to anything which actually exists.

5.7.4. Assignment

You can use pointers with the assignment operators if the following conditions are met:

In the last two cases, the type pointed to by the left-hand side must have at least the same qualifiers as the type pointed to by the right-hand side (possibly more).

So, you can assign a pointer to int to a pointer to const int (more qualifiers on the left than the right) but you cannot assign a pointer to const int to a pointer to int. If you think about it, it makes sense.

The += and -= operators can involve pointers as long as the left-hand side is a pointer to an object and the right-hand side is an integral expression. The arithmetic rules above describe what happens.

5.7.5. Conditional operator

The description of the behaviour of this operator when it is used with pointers has already been given in Chapter 3.