Monday, October 6, 2008

dubious pointers

C 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 int (say) and you cannot assign one to the other, compare them, substitute one for the other as an argument to a
function .... in fact they may even be stored differently in memory and even be of different lengths.

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
void’. This is one of the Standard's invented features: before, it was tacitly assumed that ‘pointer to char’ was adequate for the task. This has been a reasonably
successful assumption, but was a rather untidy thing to do; the new solution is both safer and less misleading. There isn't any other use for a pointer of that 0
type—void * can't actually point to anything—so it improves readability. A pointer of type void * can have the value of any other pointer assigned to and can,
conversely, be assigned to any other pointer. This must be used with great care, because you can end up in some heinous situations. We'll see it being used safely later with the malloc library function.
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 0 or that value converted to type void * by using a cast. Both versions are called the null pointer constant. If you assign a null pointer constant to any other pointer, or compare it for equality with any other pointer, then it is first converted the type of that other pointer (neatly solving any problems about type compatibility) and will not appear to have a value that is equal to a pointer to any object in the program.

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 FF is written into wherever the pointer is pointing. The int at location 6 has had0xFF written into it—subject to whatever ‘location 6’ means on this particular machine.
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.

No comments: