Home : C++ : Pointers

Pointers and Memory

One of the hardest topics to understand when learning to program is the use of pointers. Whilst it is a tricky subject, every programmer must learn this stuff whether you're writing VB, Java, Perl or C/C++.

Pointers work with your computer and using them correctly will make your programs much faster and let you sleep safe at night.

This article attempts to explain what pointers are, how to use them and why to use them, from the ground up. If you read this article and understand it all then good for you, a long and prosperous life awaits. If you read it and don't understand it all, don't panic - you will only suffer mild hair-loss in later life. If you don't bother to read it, you'll get hemarroids within one hour.

What Is Memory?

In order to understand pointers, you must first know what memory is since pointers can only point to memory.

Memory is a sequence of bytes, loads of them - a 128Mb machine has 134,217,728 memory locations. Each byte is accessed by a specific address, i.e. a number and consecutive memory locations are numbered consecutively.

When your program runs, it must share these locations with other programs including the operating system, device drivers etc. So for your program to access memory, it has to request it from the operating system. There are two ways to do this, the heap and the stack which are discussed later. Your program will never be given a memory location of 0 as that corresponds to an out of memory condition. The operating will always provide you with the space you asked for or an out of memory condition.

The important concept here, is that every program has its own bit of memory which it may use however it wants - storing graphics, frag limits, quadratic equations, whatever.

So, lets say we ask for 20 bytes to store a person's name, what do we get? If there are 20 bytes available to us, we'll be given a memory location N. This means that the 20 bytes from N+0 to N+19 are ours to do with what we want. Check out my groovy diagramming skills below:

Memory Diagram

The diagram shows the 20 bytes we requested as black squares whilst the rest of the memory - which we are not allowed to read or write is shown in red.

So that covers, the basics of memory. More advanced technical stuff is covered in the section on memory paging later in the document.

What are Pointers?

A pointer is a sequence of memory locations that contain a memory address. In those languages that support pointers (C, C++, Pascal etc), the pointer is just a variable. In languages that lack pointer support (Java, Perl etc), the pointer still exists but it is hidden from the programmer by the run-time environment.

So, if we expand the diagram from the previous section to include our new pointer P, it will look like this:

Memory Diagram

Our pointer location, P now points to memory location N - the start of the memory block we allocated in the previous section. Note that I have chosen to represent P with four bytes (four black squares), this is means that our pointers are 32-bits size (as are most machines today).

Diagrams like the one above are the best way to visualise what is going on in memory with pointers and allocated memory. If you hit a tricky problem when programming with pointers, get a piece of graph paper and draw what you think is going on in memory to help work through the problem.

The Heap and the Stack

There are two ways in which a program can get its hands on memory - from the heap or from the stack. The heap is an area of memory from which the program can explicitly request memory using functions like new() and malloc(). The program has access to that memory until it explictly releases it by calling delete() or free(). A program which gets memory from the heap but which doesn't bother to deallocate it, is said to have memory leaks.

Memory allocation and deallocation from the stack is handled automatically by the compiler. Whenever a function is called, its parameters are allocated from the stack without the programmer needing to perform any extra work. When the function exits, the parameters are automatically deallocated. Similarly, local variables are allocated from the stack and deallocated when they go out of scope.

The stack works in a last in, first out (LIFO) manner because the last thing allocated from the stack is always the first thing to be deallocated. Because of this, memory allocation from the stack is much faster than using the heap.

The stack can cause further problems for developers working with pointers because memory is automatically deallocated. If a function returns a pointer to a local (automatic) variable, that variable will be deallocated when the function exits and the pointer will now point to invalid memory.

Word Boundaries

Variables of the common, built in types have different sizes, characters are usually 1 byte, integers often 4 bytes (32 bit) whilst doubles are 8 bytes. The computer's CPU is designed with this in mind and its built in instructions are implemented to make use of the fact. This means that, for example, a CPU instruction to move a 32 bit integer in memory is significantly more efficient when the integer is stored on a 32 bit boundary - i.e. its memory address is divisible by four.

So what are the consequences of this? Well, this is important when you're working with bits & bytes. Imagine you have a serial device which will handle a message consisting of an 8 bit command identifier followed by a 32 bit data item. An engineer who had never heard of word alignment would write the following C code to describe the message:

    typedef struct SDeviceMsg {
        unsigned char m_ucCmdID;
        unsigned int m_uiDataItem;
    };

        .
        .
        .

    SDeviceMsg msg = { 1, 2 };
    SendToSerialPort(&msg);

The author thinks that he/she can take the address of an SDeviceMsg structure and expect an 8 bit value followed immediately by a 32 bit value - a total size of 5 bytes. "But!", the smart cookie reader exclaims, "What about word alignment, that code will crash and burn like a dog. Ha, ha, ha, haaaaaaa!".

The smart cookie reader is correct, but there's no need for such maniacal cackling. The 32 bit value, m_uiDataItem, must be aligned on a 4 byte boundary but the unsigned char, m_ucCmdID, need only be aligned against a single byte. The structure size is in fact 8 bytes:

It should be noted that the start of all allocated structures is aligned with the largest type supported by the CPU - usually the double weighing in at a hefty 8 bytes. This is because functions like malloc() allocate raw memory which is then cast to the desired type. So the results of the following malloc() are always aligned on a 8 byte boundary, otherwise dereferencing pD would result in unaligned data:

   double *pD = (double *)malloc(sizeof(double));

Many CPU's will cope if data is not aligned. Sun machines will do this without a whimper, DEC UNIX machines print a warning message on stderr but HPUX machines fire a bus error which often corrupts the stack also. So if you are writing portable code, you should always respect the need for word alignment or suffer the consequences.

Memory Paging

Operating systems that make use of virtual memory, provide memory to a process in a series of pages. Pages can then be swapped to and from the hard disk as they are needed. A memory page need not always be loaded into the same portion of physical RAM each time it is restored from disk - this means that your pointers do not contain physical memory addresses, they store a page identifier and an offset.

What Happens When You Make A Mistake With Pointers

There are great many nasty things that can happen when you get pointers wrong, your dog could die, spouse walk out on you and children grow up to be politicians, but on UNIX, the most common are as follows:

The first two types of problem are the most common and can usually be detected an fixed with the help of a good debugger, but the last two are much more difficult to correct. To help you, there are a number of great tools on the market such as Rational's Purify and Numega's Bounds Checker. These tools add extra instructions to your executable to find nasty errors as they happen. Some debuggers even attempt to perform similar checks but they are nowhere near as effective.

Pointer Terminilogy

This section introduces some common pointer related terms and explains what is really mean by all those big words:

Comments

Members have left 0 comments about this page:
Please Login or Register to comment on this page.

Resources
Tools
User
Last Updated Wednesday, 24-May-2006 22:39:17 BST