Monday, 29 January 2007

Pointers demystified

Note: The RichText blog has moved to www.ricroberts.com

Over the last few months, my day-job has required that I do a bit of C++ development. Previous to this, the last time I did commercial C++ was in one of my first developer roles back in 2002, so I've recently been rediscovering it's joys.

The thought of C++ often scares people, but that needn't be the case. I was lucky enough to come to C# after learning C++, but the transition the other way shouldn't be that difficult either. In fact, I would definitely recommend that C-sharpers get to know C++, if they don't already, as it provides a greater understanding of C#'s inner workings..

Much of the syntax is similar, and one of few big differences is memory management (in the form of pointers). This post hopes to demystify pointers and thus hopefully remove one of the final barriers from people having a go at C++.

What the heck is a pointer anyway?

A pointer is just a variable that holds a memory address. (i.e. the location of a value in memory). They perform a similar function to reference-types in C#, which have a value on the stack which is just an address of somewhere on the heap.

Making a pointer

To make a pointer, you just declare variable of a certain type, but precede the name with an asterisk:

int *pMyPointer = NULL;
(I just said that a pointer is a variable that holds a memory address, so you might be thinking: "Why do you need to give it a specific type?". Well, the type you specify is the type of the variable that will be stored at the memory address to which the pointer "points". This lets the compiler know how much space to reserve at that memory location e.g. 4 bytes for a 32 bit integer etc.)

Anyway, in the example above, I created a pointer which will point to an integer, and I gave it the value NULL. i.e. it points to nothing yet. (Note that NULL in C++ just means 0).

The opposite of a pointer

The opposite of a pointer, I suppose, is the address-of operator (&). This lets you get the memory address of a variable.
int myCount = 132;
pMyPointer = &myCount;
Here, the address of the myCount variable is stored in the pMyPointer pointer. So now, it points to our integer with value 132.

Assigning values at memory locations

You can assign to the location in memory to which you're pointer points by using the syntax:
*pMyPointer = 12;
Here I've asked to store the value 12 in the memory address of the pointer pMyPointer. i.e. where we've stored myCount. This is called indirection. (Notice that the asterisk means a different thing here, than when you declared your pointer. Here the asterisk means "the value held at...").

You can also use indirection to get stuff back out of addresses referenced by pointers. e.g. to get the value held at the address in pMyPointer:
int myNumber;
myNumber = *pMyPointer;
Since we've changed the value stored at the location to which pMyPointer points, myNumber will get assigned the value 12.

Heaping it up

In C++, when you use the 'new' keyword, it returns a memory address on the heap, and you need to assign it to a pointer. e.g. to assign enough space on the heap for an int, and put the memory address in a pointer called pYourPointer:
int *pYourPointer = new int;
Then you can assign a value like before.
*pYourPointer = 500;

My memory is leaking!

Now, when you're working with things on the heap, they don't go out of scope when your method ends, unlike variables on the stack. As pointers are variables like any other, if ones go out of scope you lose the only thing that told your program where to look in memory for your precious object. That memory is now unavailable until the program quits. Continuing with our example, adding the line:
delete pYourPointer;
deletes/frees up the memory that the pYourPointer pointer points to, so it can be used again. If we forgot to do that, and then pYourPointer went out of scope, we would have 'leaked' memory.

You can also leak memory if you reassign a pointer without calling delete first. e.g. if we didn't call delete on the 2nd line of this bit of code.
MyObject *pPointy = new MyObject(1,3);
delete pPointy;
pPointy = new MyObject(4,5);
The final line above sets pPointy to another memory address, and without deleting the contents at the original location, it would be lost forever! (or at least until the program ends). You might think: "My computer's got loads of memory, and the program will end soon enough!"... but what if it was a Windows Service that ran indefinitely? If there were lots of loops leaking memory then it might not be long until you ran out.

After you call delete on an object, for safety's sake you should always null-out the pointer to avoid leaving a pointer dangling, and accidentally using the deleted pointer again (which would cause your app to crash). Note that it's always safe to call delete on a null pointer.

pPointy = NULL;

Destructors

The example above illustrates another point (no pun intended). Calling delete on an object calls its destructor. If your class MyObject had member variables which themselves were stored on the heap, then you would delete them in the destructor. C++ destructor has the same syntax as a C# destructor i.e.
~MyObject()
{
// do cleaning up here!
}
but C++ destructors are called manually, rather than by the Garbage Collector as in C#.

Geting at your members

One of the weirdest things for me, coming back to C++ after years programming mainly C# was getting used the way you access members on objects on the heap again. Say you've got a pointer for a Person object, and the Person class had a GetNumberOfToes() method, you'd use the points-to operator (->) to call it. (Note that the familiar dot operator is used for objects on the stack). e.g.
Person *pMyPerson = new Person();
int toes = pMyPerson->GetNumberOfToes();
delete pMyPerson;
Note: You can actually call the method on the objected pointed to by pMyPerson like this:
(*pMyPerson).GetNumberOfToes();
i.e. call GetNumberOfToes() on the object held at the memory location pointed to by pMyPerson... but that just looks naff.

Another use for the Ampersand... References

Having read all that, you might start poking round some C++ projects that you've got access to, but were previously too scared to open. For completeness, I thought that I would continue to explain another use for the & operator, so you don't end up completely confused when you see it everywhere.

The & operator is also used for references. References are really just a way of giving an object another name. Anything done to the reference is done to the original. e.g.
int myInt;
int &refToMyInt = myInt;
Now assigning a value to either, will set the same value at a single memory location. e.g.
refToMyInt = 930; //actually sets myInt to 930


Passing by reference

This is a familiar concept to C# programmers, and is simple in C++ too. You just declare a function with the parameters that you want to pass by reference with preceding ampersands. e.g.
int DoSomething(int &intOne, int &intTwo)
{
intOne = intOne + intTwo;
return intOne;
}
and when you call it, just pass the values in 'as normal':
int anInt = 1, anotherInt = 2;
DoSomething(anInt, anotherInt);
The integers passed in can be changed by the body of the method, making anInt 3 and anotherInt 2.

Want more?

There's loads more I could talk about here (I'm getting a bit carried away!), but I'd be here all night if I carried on. If you want to know more, buy a book!



Digg Technorati del.icio.us Stumbleupon Reddit Blinklist Furl Spurl Yahoo Simpy

Please also visit the Swirrl blog

1 comment:

carlos said...

Hello, I found your post very useful as I, too, am coming back to C++ development after almost ten years since graduation!

It was a good refresher course and probably stopped me going slowly mad as I tried to make sense of this mess of ampersands and asterisks everywhere!

Thanks.