Understanding pointers in C

When I was first introduced to the C programming language, probably some time around 1984, I just didn't understand pointers, the notation didn't seem to make sense and the texts available at the time seemed both confused and incomplete.

Eventually I managed to grasp enough to read and use basic pointer functionality, but the full extent to which pointers and different types interacted eluded me until many years later.

Starting with the basics, we can create a pointer to any native data type, so for example we have a char data type capable of storing one byte, and we can create a pointer to it like so;

char a = "A";
char *pointer = &a;   // '&' gets the variable's address
printf("A=%c\n",a);
printf("Pointer=%p\n",pointer);
printf("*Pointer=%c\n",*pointer);  // '*' dereferences the pointer

Which looks like this when it runs;

$ ./test
A=A
Pointer=0x7ffea1299c5f
*Pointer=A

We can do the same things with integers;

int a = 123;
int *pointer = &a;
printf("A=%d\n",a);
printf("Pointer=%p\n",pointer);
printf("*Pointer=%d\n",*pointer);

Which looks like this when it runs;

$ ./test
A=123
Pointer=0x7fff303994dc
*Pointer=123

The first point of confusion comes when people start to use arrays, these tend to be easier to read than pointers, however, they are actually implemented as pointers, indeed they are interchangeable with pointers, so if you're used to using pointers, seeing arrays in use instead can be counter intuitive. Consider;

int a[10];      // define an array of 10 integers
int x;
for(x=0;x<10;x++) a[x]=x+1; // insert arbitrary data
int *pointer=a;
printf("*Pointer=%d\n",*pointer++);
printf("*Pointer=%d\n",*pointer++);

Which comes out like this;

$ ./test
*Pointer=1
*Pointer=2

Whoa. So what just happened - there's a bunch of stuff there that doesn't look right?! Well, first, because arrays are implemented as lists of pointers, a[10] is just a contiguous block of 10 integers in memory and a is a pointer to the beginning of that block. So a is a pointer, effectively to the first integer in the block of integers, so it's quite valid to assign an integer pointer to it. So after int *pointer=a, pointer effectively points to the first integer in the array that we've initialised to '1'.

Now, when we add '++' to the end of an integer, it adds one to the integer. However, when we add '++' to the end of a pointer, it increments the pointer value by the size of the datatype the pointer points to. So essentially when you see pointer++, what it's actually doing is pointer = pointer + sizeof(int). So after this operation, pointer will be pointing to the 'next' element in the array, or the next element in the memory block of integers.

Now this is where it starts to get really fun, what happens when we try to do this with a string type, well it goes something like this;

char *names[10];
int x;
for(x=0;x<10;x++) {
  names[x] = (char*)malloc(32);
  sprintf(names[x],"Hello-%d",x);
}
char **pointer=names;
printf("*Pointer=%s\n",*pointer++);  
printf("*Pointer=%s\n",*pointer++);  

Which of course gives;

$ ./test
*Pointer=Hello-0
*Pointer=Hello-1

Again, at first glance (!) that just doesn't look right, what's with the double asterisk?! Well, a string is an array of characters, and an array of strings is an array of array of characters. So if you point at a string you need to be using a pointer, but if you point at an array of strings, you need a pointer to a pointer, which is implemented in C as a double asterisk. So because the pointer type is actually a string, when you autoincrement it (++) then you add the size of an array element to the pointer, in this case then length of a string element, hence pointer++ can be used to iterate through the array.

And if you start to move into the realms of multi-dimensional arrays, then you can see where this is going in terms of the number of asterisks we're going to be using to define a pointer, a two dimensional array of strings requires (for example) a pointer to a pointer to a pointer.

There's a very common gotcha that follows on from this, when you use sizeof(int) then you get a 4 back if your system uses 4-byte integers. If however you use sizeof() on an array, it does not return the number of elements dimensioned in the array, it returns the amount of memory consumed by the array. So if you do a sizeof on an array of 10 integers, you'll be seeing a 40 returned, and not a 10. So what you do not want in your code is this;

int x,a[10];
for(x=0;x<sizeof(a);x++) a[x]=x;

Although intuitive, this will cause a memory overflow and it will not make your computer happy. Technically the correct way to write this would be;

int x,a[10];
for(x=0;x<sizeof(a)/sizeof(*a);x++) a[x]=x;

or alternatively;

int x,a[10],*p=a;
for(x=0;x<sizeof(a)/sizeof(*a);x++) *p++=x;

or maybe even;

int b[10],*p=&b[9],val=10;
while(p>=b) *p--=val--;

Alternatively, Python doesn't really need or use the concept of pointers, so if you don't need the extreme speed, it may save much brain-ache .. :-)