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.
/*
An Example :
#include
<stdlib.h>
/*
}
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 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;
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_ {
};
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 */
/*
access data item with the '.' (dot) operator */
... 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 ) {
}
void
SomeFunc(int
arg)
} int
var = 1;
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
)
}
Further Reading :
|