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)
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:
- 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.
- 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
volatile, or both; the examples are illustrated with const
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.
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
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
(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
is defined in the header file
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; int *pi, *cpi = &x; /* 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");
These allow us to compare pointers with each other. You can only compare
- Pointers to compatible object types with each other
- Pointers to compatible incomplete types with each other
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.
You can use pointers with the assignment operators if the following conditions are met:
- The left-hand operand is a pointer and the right-hand operand is a null pointer constant.
- One operand is a pointer to an object or incomplete type; the other
is a pointer to
void(whether qualified or not).
- Both of the operands are pointers to compatible types (whether qualified or not).
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
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.
-= 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.
The description of the behaviour of this operator when it is used with pointers has already been given in Chapter 3.