5.3. PointersUsing pointers is a bit like riding a bicycle. Just when you think that you'll never understand them—suddenly you do! Once learned the trick is hard to forget. There's no real magic to pointers, and a lot of readers will already be familiar with their use. The only peculiarity of C is how heavily it relies on the use of pointers, compared with other languages, and the relatively permissive view of what you can do with them. 5.3.1. Declaring pointersOf course, just like other variables, you have to declare pointers before you can use them. Pointer declarations look much like other declarations: but don't be misled. When pointers are declared, the keyword at the beginning (c int, char and so on) declares the type of variable that the pointer will point to. The pointer itself is not of that type, it is of type pointer to that type. A given pointer only points to one particular type, not to all possible types. Here's the declaration of an array and a pointer: int ar[5], *ip; We now have an array and a pointer (see Figure 5.3): The ip = 6; What would Here is the right way to initialize a pointer: int ar[5], *ip; ip = &ar[3]; In that example, the pointer is made to point to the member of the
array You can see that the variable What is this new unary We will continue to use the term ‘address of’ though, because to invent a different one would be even worse. Applying the
int i;
float f;
/* '&i' would be of type pointer to int */
/* '&f' would be of type pointer to float */
In each case the pointer would point to the object named in the expression. A pointer is only useful if there's some way of getting at the thing
that it points to; C uses the unary
#include <stdio.h>
#include <stdlib.h>
main(){
int x, *p;
p = &x; /* initialise pointer */
*p = 0; /* set x to zero */
printf("x is %d\n", x);
printf("*p is %d\n", *p);
*p += 1; /* increment what p points to */
printf("x is %d\n", x);
(*p)++; /* increment what p points to */
printf("x is %d\n", x);
exit(EXIT_SUCCESS);
}Example 5.1You might be interested to note that, since The precedence of So, provided that a pointer holds the address of something, the
notation
#include <stdio.h>
#include <stdlib.h>
void
date(int *, int *); /* declare the function */
main(){
int month, day;
date (&day, &month);
printf("day is %d, month is %d\n", day, month);
exit(EXIT_SUCCESS);
}
void
date(int *day_p, int *month_p){
int day_ret, month_ret;
/*
* At this point, calculate the day and month
* values in day_ret and month_ret respectively.
*/
*day_p = day_ret;
*month_p = month_ret;
}Example 5.2Notice carefully the advance declaration of When The arguments have been passed to One of the great benefits introduced by the new Standard is that it allows the types of the arguments to date to be declared in advance. A great favourite (and disastrous) mistake in C is to forget that a function expects pointers as its arguments, and to pass something else instead. Imagine what would have happened if the call of date above had read date(day, month); and no previous declaration of date had been visible. The compiler
would not have known that date expects pointers as arguments, so it
would pass the Fortunately, by declaring Perhaps surprisingly, it isn't all that common to see pointers used to give this call-by-reference functionality. In the majority of cases, call-by-value and a single return value are adequate. What is much more common is to use pointers to ‘walk’ along arrays. 5.3.2. Arrays and pointersArray elements are just like other variables: they have addresses. int ar[20], *ip; ip = &ar[5]; *ip = 0; /* equivalent to ar[5] = 0; */ The address of Adding an integral value to a pointer results in another pointer of the same type. Adding n gives a pointer which points n elements further along an array than the original pointer did. (Since n can be negative, subtraction is obviously possible too.) In the example above, a statement of the form *(ip+1) = 0; would set
int ar[20], *ip;
for(ip = &ar[0]; ip < &ar[20]; ip++)
*ip = 0;
That example is a classic fragment of C. A pointer is set to
point to the start of an array, then, while it still points inside the
array, array elements are accessed one by one, the pointer incrementing
between each one. The Standard endorses existing practice by
guaranteeing that it's permissible to use the address of
Why is the example better than indexing? Well, most arrays are
accessed sequentially. Very few programming examples actually make use
of the ‘random access’ feature of arrays. If you do just want
sequential access, using a pointer can give a worthwhile improvement in
speed. In terms of the underlying address arithmetic, on most
architectures it takes one multiplication and one addition to access
a one-dimensional array through a subscript. Pointers require no
arithmetic at all—they nearly always hold the store address of the
object that they refer to. In the example above, the only arithmetic
that has to be done is in the
int ar[20], i;
for(i = 0; i < 20; i++)
ar[i] = 0;
The same amount of arithmetic occurs in the loop statement, but an extra address calculation has to be performed for every array access. Efficiency is not normally an important issue, but here it can be. Loops often get traversed a substantial number of times, and every microsecond saved in a big loop can matter. It isn't always easy for even a smart compiler to recognize that this is the sort of code that could be ‘pointerized’ behind the scenes, and to convert from indexing (what the programmer wrote) to actually use a pointer in the generated code. If you have found things easy so far, read on. If not, it's a good idea to skip to Section 5.3.3. What follows, while interesting, isn't essential. It has been known to frighten even experienced C programmers. To be honest, C doesn't really ‘understand’ array indexing,
except in declarations. As far as the compiler is concerned, an
expression like
#include <stdio.h>
#include <stdlib.h>
#define ARSZ 20
main(){
int ar[ARSZ], i;
for(i = 0; i < ARSZ; i++){
ar[i] = i;
i[ar]++;
printf("ar[%d] now = %d\n", i, ar[i]);
}
printf("15[ar] = %d\n", 15[ar]);
exit(EXIT_SUCCESS);
}Example 5.3Summary
5.3.3. Qualified typesIf you are confident that you have got a good grasp of the basic declaration and use of pointers we can continue. If not, it's important to go back over the previous material and make sure that there is nothing in it that you still find obscure; although what comes next looks more complicated than it really is, there's no need to make it worse by starting unprepared. The Standard introduces two things called type qualifiers,
neither of which were in Old C. They can be applied to any declared type
to modify its behaviour—hence the term ‘qualifier’—and
although one of them can be ignored for the moment (the one named
If a declaration is prefixed with the keyword There are two benefits in being able to declare things to be
Of course, constants are not much use unless you can assign an initial value to them. We won't go into the rules about initialization here (they are in Chapter 6), but for the moment just note that any declaration can also assign the value of a constant expression to the thing being declared. Here are some example declarations involving const:
const int x = 1; /* x is constant */
const float f = 3.5; /* f is constant */
const char y[10]; /* y is an array of 10 const ints */
/* don't think about initializing it yet! */
What is more interesting is that pointers can have this qualifier applied in two ways: either to the thing that it points to (pointer to const), or to the pointer itself (constant pointer). Here are examples of that:
int i; /* i is an ordinary int */
const int ci = 1; /* ci is a constant int */
int *pi; /* pi is a pointer to an int */
const int *pci; /* pc is a pointer to a constant int */
/* and now the more complicated stuff */
/* cpi is a constant pointer to an int */
int *const cpi = &i;
/* cpci is a constant pointer to an constant int */
const int *const cpci = &ci;
The first declaration (of It isn't hard to understand what a pointer to an integer and a pointer
to a constant integer do—but note that they are different types of
pointer now and can't be freely intermixed. You can change the values of
both The last two declarations are the most complicated. If the pointers
themselves are constant, then you are not allowed to make them point
somewhere else—so they need to be initialized, just like
A final piece of clarification: what constitutes a qualified type? In
the example, Although the declarations do take some mental gymnastics to understand, it just takes a little time to get used to seeing them, after which you will find that they seem quite natural. The complications come later when we have to explain whether or not you are allowed to (say) compare an ordinary pointer with a constant pointer, and if so, what does it mean? Most of those rules are ‘obvious’ but they do have to be stated. Type qualifiers are given a further airing in Chapter 8. 5.3.4. Pointer arithmeticAlthough a more rigorous description of pointer arithmetic is given later, we'll start with an approximate version that will do for the moment. Not only can you add an integral value to a pointer, but you can also
compare or subtract two pointers of the same type. They must both point
into the same array, or the result is undefined. The difference between
two pointers is defined to be the number of array elements separating
them; the type of this difference is implementation defined and will be
one of In an expression the name of an array is converted to a pointer to
the first element of the array. The only places where that is not
true are when an array name is used in conjunction with
#include <stdio.h>
#include <stdlib.h>
#define ARSZ 10
main(){
float fa[ARSZ], *fp1, *fp2;
fp1 = fp2 = fa; /* address of first element */
while(fp2 != &fa[ARSZ]){
printf("Difference: %d\n", (int)(fp2-fp1));
fp2++;
}
exit(EXIT_SUCCESS);
}Example 5.4The pointer Unfortunately, if the difference does happen to be
#include <stdio.h>
#define ARSZ 10
main(){
float fa[ARSZ], *fp1, *fp2;
fp1 = fp2 = fa; /* address of first element */
while(fp2 != &fa[ARSZ]){
printf("Difference: %ld\n", (long)(fp2-fp1));
fp2++;
}
return(0);
}Example 5.55.3.5. void, null and dubious pointersC is careful to keep track of the type of each pointer and will not in
general allow you to use pointers of different types in the same
expression. A pointer to char is a different type of pointer from
a pointer to Pointers of different types are not the same. There are no implicit conversions from one to the other (unlike the arithmetic types). There are a few occasions when you do want to be able to sidestep some of those restrictions, so what can you do? The solution is to use the special type, introduced for this purpose,
of ‘pointer to You may also on occasion want a pointer that is guaranteed not to point to any object—the so-called null pointer. It's common practice in C to write routines that return pointers. If, for some reason, they can't return a valid pointer (perhaps in case of an error), then they will indicate failure by returning a null pointer instead. An example could be a table lookup routine, which returns a pointer to the object searched for if it is in the table, or a null pointer if it is not. How do you write a null pointer? There are two ways of doing it and
both of them are equivalent: either an integral constant with the value
of The only values that can be assigned to pointers apart from 0 are the values of other pointers of the same type. However, one of the things that makes C a useful replacement for assembly language is that it allows you to do the sort of things that most other languages prevent. Try this: int *ip; ip = (int *)6; *ip = 0xFF; What does that do? The pointer has been initialized to the value of
6 (notice the cast to turn an integer 6 into a pointer). This is
a highly machine-specific operation, and the bit pattern that ends up in
the pointer is quite possibly nothing like the machine representation of
6. After the initialization, hexadecimal It may or may not make sense to do that sort of thing; C gives you the power to express it, it's up to you to get it right. As always, it's possible to do things like this by accident, too, and to be very surprised by the results. |
The C BookThis book is published as a matter of historical interest. Please read the copyright and disclaimer information. GBdirect Ltd provides up-to-date training and consultancy in C, Embedded C, C++ and a wide range of other subjects based on open standards if you happen to be interested. |
|
West Yorkshire Office
GBdirect Ltd
Training: 0800 651 0338 Please call between 0900 and 1700 (UK time) on Monday to Friday South East Regional Office
GBdirect Ltd
Training: 0800 651 0338 Please call between 0900 and 1700 (UK time) on Monday to Friday Please note: |