Pointers Of Doom

 

Oh dear, the joy of programming is just about to reach a new level. C provides functionality to directly access the memory of the computer to gain complete control over every aspect of the computer. Explaining pointers is the most difficult task in teaching C/C++. The syntax is horrible, the concepts are abstract and it all seems a bit grim. I am about to explode your mind to start showing you the power of pointers and real world practical applications of them. Over the coming weeks we will start looking in detail at all of the concepts presented today so that by next term you will be pointer ninja's!

 

The Basics

 

A pointer is a variable that holds the memory location of another variable. All variables you have declared in your programs so far actually existed somewhere within the memory of the computer :


int SomeVariable = 10;


 

Now, we can find out the address of 'SomeVariable' by using the unary address operator (&), for example :

 


printf( "Memory Address = %d\n", &SomeVariable );


 

A pointer is essentually a special kind of variable that holds an address to a piece of this data. For example the following creates a variable called intPtr that holds the memory location of our variable :

 


int* intPtr = &SomeVariable;


 

This declares a variable called intPtr that holds the memory location of an integer.

If we wish to read or write to the variable SomeVariable, we can go to the memory location specified by intPtr and edit or read the variable. We term this de-referencing.

 

 

Quick Recap

Memory is just a big array of bytes

A Pointer is therefore an INTEGER index into that array.

Use the unary & operator to get it's address

Use the unary * operator to access data at the end of the pointer

 

The void Pointer

As you may recall, pointer declaration goes something like :


int* intPtr = &SomeVariable;


We get two pieces of information from this declaration, firstly the * indicates that this variable holds an address, and secondly that the variable at that memory location is an integer. We can also declare whats known as a void pointer...


void* Ptr = intPtr;


This pointer is used as a generic memory address, no information is actually known about what you will find at the end of the pointer. Now you will notice that there is no problem in converting from an int* to a void*. This allows us to do an assignment from type int* to void*. We are going from a pointer with information about it's type to a pointer that has no information about type. In effect, because the assignment of intPtr to Ptr loses no data, it is perfectly valid.

However, converting the other way around causes problems, this code will not compile!


void* Ptr = &SomeVariable;

int* intPtr = Ptr;


Well, this is quite interesting. Ptr can be initialised with the address of SomeVariable. In the second line we try and initialise intPtr from Ptr. The compiler now has a distinct problem, it looks at the comparison and says, well we are storing one address in another, that's ok; however intPtr also needs to know that an integer occurs at the end of the memory location. Ptr however contains no information about type so we have to tell the compiler that it's ok to perform the conversion using a cast :


void* Ptr = &SomeVariable;

int* intPtr = (int*)Ptr;


 

De-referencing

In order to get access to the variable declared at the end of the pointer, we have to De-reference the pointer. This is done with the unary * operator.

 


/*
* goto the memory location specified by intPtr.
* Assign the value 12 into that memory
*/

*intPtr = 12;


 

An Example :

 


#include <stdlib.h>
#include <stdio.h>


/*
* We will adjust the value of this global variable without touching it
*/

int SomeVariable = 10;

/*
* The main function
*/

int main( int argc,char **argv)
{

 

/*
* Create a pointer to an integer. We always assign pointers
* to NULL if they are not currently used. Be sure to stick to
* this method because it gives us an easy way to find out if
* a given pointer is valid or not. fyi: NULL == 0
*/

int* ptrInt = NULL;

/*
* What's the current value in SomeVariable
*/

printf("Current value of SomeVariable is : %d\n",SomeVariable);

/*
* Store the address of SomeVariable in the pointer. & returns
* the address of the variable it is acting on.
*/

ptrInt = &SomeVariable;

/*
* We can use `ptrInt` as though it were `SomeVariable`. In order to
* do this, we need to de-reference the pointer and retrieve the value
* of the variable it is pointing to. This is done with the dereferencing
* operator '*', ie
*/

*ptrInt = 12;

/*
* Essentually "*ptrInt = 12;" has the exactly the same effect as
* "SomeVariable = 12;". To prove it, print the variable again....
*/

printf("New value of SomeVariable is : %d\n",SomeVariable);

/*
* And you now probably think "What is the point in that?" .....
*/

return 0;

}


 

The NULL Convention

When using pointers we have to be extremely careful. We have been granted special priviledges to access the memory directly, as you will soon see, this gives us the ability to do some fairly amazing things. There is however a downside, on the average computer, there will be a number of applications running, each utilizing a chunk of the systems memory, we could picture it like this :

We really do want to make sure that we only use the pointer to access data that we know to be valid. If we start altering data at the end of an invalid pointer we don't really know what will happen. If you feel brave, try running this on your computers at home (please don't try at university, I will not forgive you for it).


#include <stdlib.h>

/*
* The main function
*/

int main()
{

 

/*
* Create a pointer to a bit of data...
*/

unsigned int* ptr;

/*
* loop forever
*/
w
hile(1) {

 

/*
* generate a random memory address using rand()
*/

ptr = (unsigned int*)rand();

/*
* Assign a random value to the random memory location
*/

*ptr = rand();

}

return 0;

}


 

The code above generates a random number which it uses as a memory location, and then generates another random number which it then places into the memory address. What this code does, is anilate our computer :

To minimise the possibility of the situation above ever happening, we must always set unused pointers to NULL, and then test all pointers to see if they are NULL before using them. It might seem long winded, however in the long run it makes your life ten times easier when writing complex projects.

 

Structures

A slight detour is afoot! I briefly want to mention the topic of structures in case you haven't seen them before. There are occasions when programming that we may want to create our own custom data types. For example, we have been looking at mesh data and have seen that we require a couple of bits of data:

 

int numFaces;
float VertexData[100000];

 

this is an OK way of representing things, but it would be nice if we could create a data type called "mesh" that we could use to represent our data. This can be done using a struct as follows :

 

struct mesh_ {

  int numFaces;
float VertexData[100000];

};

 

the above code packages together two variables, a face count and a vertexData array. Unfortunately ANSI C says that the name of this data type is "struct mesh_", as apposed to "mesh_" or anything nice like that. ie, In order to create a mesh type we would say :

 

struct mesh_ MyMesh;

/* access data items with the '.' (dot) operator */
MyMesh.numFaces = 0;


typedef



The name "struct mesh_" for the data type is a little bit shit. It would be nice if we could use something readable like "mesh". This can actually be done with the use of typedef, for example


typedef struct mesh_ mesh;


now we can use bits of code like this :


mesh MyMesh;

/* access data item with the '.' (dot) operator */
MyMesh.numFaces = 0;

 

... which is nice.

 

Passing Structures To Functions

 

At some point we can easily imagine that we would like to create a function that accepted an argument of our own custom type. For example, a DrawMesh function that accepted a mesh structure as the argument. So we think, write a function like this :



void DrawMesh( mesh MeshToDraw )
{
  int i;
glBegin(GL_TRIANGLES);
for( i=0; i<MeshToDraw.numFaces*9*8; i+=8)
{
  glTexCoord2fv( &MeshToDraw.VertexData[ i+6 ] );
glNormal3fv( &MeshToDraw.VertexData[ i+3 ] );
glVertex3fv( &MeshToDraw.VertexData[ i ] );
}
glEnd();

}




Now, this will actually work, however you probably would not want to use it ! Consider this piece of code :

 


void SomeFunc(int arg)
{

  /* changing the value of arg has no effect on var below*/
arg = 2;

}

int var = 1;

/* call the function, essentually this says arg = var */

SomeFunc( var );

/* var will still be 1... */

printf( "var is %d\n", var );


 

 

This may hint at a problem. Whenever a function is called, the variables passed into the function will be copied into the function argument. ie, when we call SomeFunc( var ), the value held in the variable 'var' is copied into the variable 'arg' that is used within the function.

 

So, function arguments are always copied !

 

Now lets again consider our mesh structure. It contains 100,001 4 byte elements. In total therefore, the size of one of our mesh structures is 400,004 bytes, or 400 Kb, or 0.4Mb. If we were therefore to call the function DrawMesh, the mesh passed into the function would be copied in it's entirety. What this means is that every time we call the function, almost half a meg of memory is allocated and the contents of the mesh passed in are copied into the new data. This is a tad bit wasteful!

What we want to do is to make the function go and directly access the data we want to read or edit. In order to do that, we can pass the address of the structure rather than the structure itself. The function with pointer use now looks like this :

 


void DrawMesh( mesh *MeshToDraw )
{

  int i;
glBegin(GL_TRIANGLES);
for( i=0; i<MeshToDraw->numFaces*9*8; i+=8)
{
  glTexCoord2fv( &MeshToDraw->VertexData[ i+6 ] );
glNormal3fv( &MeshToDraw->VertexData[ i+3 ] );
glVertex3fv( &MeshToDraw->VertexData[ i ] );
}
glEnd();

}


 

Further Reading :

Function Pointers