Sunday, November 25, 2007

C Powershot - Pointers

How should one interpret the following lines of C?

int *p;
"easy! p is an integer pointer!"

int **p;
"p is a pointer to an integer pointer" or perhaps you might say "p is a double pointer to an integer"

int ***p;
" would anybody use such a thing! *@#$ ?"

POWERSHOTS - Interpreting a pointer declaration
The interpretations given i n the preceding section, even if somewhat correct, do not scale and could inhibit our ability to understand alien (written by other people) code. The words influence the way we think, so it's necessary that we choose the right abstractions. For example, if a pointer is 4 bytes, why shouldn't a double pointer be 8 bytes? :-) The right abstraction would not even allow us to stray down such lines of thought!

Here's a better way to interpret pointers.

SNN1.1 Pointers are variables which can hold the address of a memory location, usually the address of a variable.

Pointer Part
Consider the statement
int *p;
What the "*p;" portion of the statement says is only this : p is a pointer variable

Let's call the "* p" portion here the pointer part of the pointer declaration. It tells us this much p is a pointer and * operation can be applied on it.

CPS1.1 Whatever follows the * symbol is the pointer variable.

SNN1.2 Pointer variables in C have the * operation (fancier name: dereference) defined on them.

The deference operation gets the contents of the memory location held in the pointer. That is, if you dereference a pointer, you get what the pointer points to.


Type Part
Consider, again, the statement:
int *p;
What the "int " part means is this: when you apply the * operator on p, what you get will be interpreted as an integer. It can be used as an integer.

Let's call the "int " portion here the type part of the pointer declaration.

CPS1.2 Whatever remains in the statement after you blank out the pointer portion will be the type of what you get when you dereference the pointer.

FINGER-HIDING TECHNIQUE: Just hide the *p section with your finger, what remains is the type part. This finger-hiding technique can come in handy in other situations as well. It's nifty and mighty useful. It is an application of what I call the typedef principle, we will come to that in a later episode.

Pointer declaration = pointer part + type part.

To reiterate, int *p means: p is a pointer, which will be dereferenced as "int"


int **p;
Pointer part: *p ====> p is a pointer
Type part: int * ====> when you dereference p, what you get should be interpreted as"int *".
You already know what int * means according to the power-shots! This has to be done repeatedly.

int *p[6][6];
Pointer part: *p =====> p is a pointer
Type part: int __ [6][6] ====> when you dereference p, what you get should be interpreted as "int [6][6]".
This is an array of integers with 6 rows and 6 columns. int

int (*p)(int i, int j);
Pointer part: *p =====> p is a pointer. The parentheses are required because otherwise due to precedence rule, the * would be associated with int and not p.
Type part: int __ (int i, int j) ==>when you dereference p, the type of data you'll get is "int (int i, int j) ".
This is an integer function which takes two parameters.
Yup, p is a function pointer. (But you do know better now, right? p is just a pointer, when you dereference it you will get something that can be used as a function)

int (*p(int a)) (int *b);
Pointer-part: *p ====> p is a pointer
Type-part: ( __ (int a)) ==> *p is a function.
So, p is a pointer to a function.
The remaining part is the type of the function.
Finally, p is a pointer to a function, which takes an integer, and returns a function which takes an int* parameter and returns an int!

Aside: Actually, the type part of the declaration is what is within the parentheses enclosing the pointer-part, but that would have confused you; also, this is not needed in the vast majority of cases. Parentheses always rule and dictate, as you should have guessed from the previous example as well!

You'd be much better off using typedefs for complex declarations like this one. But that does not mean that one should not know how exactly it is being interpreted. :-) More on typedefs later. For the time being, referring you to where this particular example was taken from.

1. Function-pointers can, on some architectures, require more space than normal pointers. If code memory uses a different addressing size/scheme, for instance. Have not encountered this though.
2. Please use parentheses liberally(but judiciously!) inside declarations and the * operator while dereferencing the pointer. These are often skipped and lead to confusing (nah, 'misinterpretable') code.