Whether you realize it or not, the vast majority of programs you have used make use of pointers in some way. Maybe you have experienced a NullPointerException at some point. As a programmer, code you write will more than likely use pointers, even if you have not implemented them yourself.

Today I'll show you how pointers work, so you may wish to check out how arrays and lists work for a programming primer. This article will be more theory based than usual, but stick with it, pointers are very complex!

Compiling Code

Before digging into pointers, you need to understand how code is built and executed -- maybe you know this already. This section will have fairly general statements -- things that apply to the majority of languages, but not necessarily all of them.

Pointers6

Let's take things back to the start. Every computer uses binary, a series of ones and zeros which make up modern technology as we know it. It's extremely difficult to code anything in binary (the files would be very confusing), as these are the raw instructions needed by your central processing unit or CPU to function. This is known as Machine Code.

The next step up from machine code is Assembly. This is a somewhat human readable format. While it is still complex to program in, it is possible. Assembly is made up of a series of simple commands to execute tasks, and is known as a low level programming language. It's possible to write complex programs, but it is difficult to express abstract concepts, and requires a lot of consideration.

Many video games and high performance applications have some of the logic written in assembly, as some real speed increases can be found if you know what you are doing. However, for the vast majority of programming projects, you don't need to know any assembly at all.

Pointers3

So if machine code is too difficult to write, and assembly is too difficult to program, what do you write code with? Here's where high level languages come in. High level languages make programs easy to write. You can program in something that resembles your native language, and it's easy to express complex algorithms. You may have heard of many high level languages (and you will definitely have used a program written in them):

  • BASIC
  • C++
  • Lisp

These languages are very old now, and many were developed in the early 1950s! Nearly every modern programming language is a high level language, including PHP and Python. There are more languages being invented every day (although there are probably enough now), but how exactly does your code still work properly if computers require machine code?

Here's where compilation comes in. A compiler is a program that converts your high level code into a form that can be executed. This could be another high level language, but it's usually assembly. Some languages (such as Python or Java) convert your code into an intermediate stage called bytecode. This will need compiling again at a later date, which is usually done on demand, such as when program runs. This is known as just in time compilation, and it's quite popular.

Memory Management

Now that you know how programming languages work, let's look at memory management in high level languages. For these examples, I'll be using pseudo code -- code written not in any specific language, but used to show concepts rather than exact syntax. Today, this will mostly resemble C++ as that's the best high level language (in my opinion).

For this section, it will help if you have an understanding of how RAM works.

Most languages have variables -- containers that store some data. You have to explicitly define the datatype. Some dynamically typed languages such as Python or PHP handle this for you, but they still have to do it.

Say you have a variable:

            int myNumber;
    

This code declares a variable called myNumber, and gives it a datatype of integer. Once compiled, the computer interprets this command as:

"Find some empty memory, and reserve a space big enough to store an integer"

Once this command has executed, that bit of memory cannot be used by another program. It does not contain any data yet, but it is reserved for your myNumber variable.

Now assign a value to your variable:

            myNumber = 10;
    

To complete this task, your computer accesses it's reserved memory location, and changes whatever value is stored there, to this new value.

Now, this is all well and good, but how do memory locations get unreserved? If programs reserved all the memory they like, the RAM would fill up immediately -- that would make for a very slow system.

Pointers4

To avoid this potential issue, many languages implement a garbage collector, used to destroy variables (and therefore release the reserved memory locations) that have gone out of scope.

You may be wondering what scope is and why it's so important. Scope defines the limits and lifespan of variables or any memory used by a program. A variable is "out of scope" when it can no longer be accessed by any code (that's when the garbage collector steps in). Here's an example:

            function maths() {
    int firstNumber = 1;
}

int secondNumber = 2;
print(firstNumber + secondNumber); // will not work
    

This example will not compile. The variable firstNumber is within the maths function, so that is it's scope. It cannot be accessed from outside of the function in which it has been declared. This is an important programming concept, and understanding it is crucial to working with pointers.

This way of handling memory is called the stack. It's the way the vast majority of programs work. You don't have to understand pointers to use it, and it's fairly well structured. The disadvantage of the stack is the speed. As the computer has to assign memory, keep track of variables, and run the garbage collection, there is a small overhead. This is fine for smaller programs, but what about high performance tasks, or data heavy applications?

Enter: pointers.

Pointers

On the surface, pointers sound simple. They reference (point to) a location in memory. This may not seem any different to "regular" variables on the stack, but trust me, there's a huge difference. Pointers are stored on the heap. This is the opposite of the stack -- it's less organized, but is much faster.

Let's look at how variables are assigned on the stack:

            int numberOne = 1;
int numberTwo = numberOne;
    

This is simple syntax; The variable numberTwo contains the number one. It's value is copied across during the assignment from the numberOne variable.

If you wanted to get the memory address of a variable, instead of it's value, you have to use the ampersand sign (&). This is called the address of operator, and is an essential part of your pointer toolkit.

            int numberOne = 1;
int numberTwo = &numberOne;
    

Now the numberTwo variable points to a memory location, rather than getting the number one copied across to it's own, new memory location. If you were to output this variable, it would not be the number one (even though that is stored in the memory location). It would output it's memory location (probably something like 2167, although it varies depending on the system, and available RAM). To access the value stored in a pointer, instead of the memory location, you have to dereference the pointer. This accesses the value directly, which would be the number one in this case. Here's how you dereference a pointer:

            int numberTwo = *numberOne;
    

The dereference operator is an asterisk (*).

This can be a difficult concept to understand, so let's go over it again:

  • The address of operator (&) stores the memory address.
  • The dereference operator (*) accesses the value.

The syntax changes slightly when declaring pointers:

            int * myPointer;
    

The datatype of int here refers to the datatype the pointer points to, and not the type of the pointer itself.

Now that you know what pointers are, you can do some really neat tricks with them! When memory is used, your operating system starts sequentially. You can think of RAM as pigeon holes. Many holes to store something, only one can be used at once. The difference here is, these pigeon holes are all numbered. When assigning memory, your operating system starts at the lowest number, and works up. It will never jump around between random numbers.

Pointers1

When working with pointers, if you have assigned an array, you can easily navigate to the next element by simple incrementing your pointer.

Here's where it gets interesting. When you pass values to a function (using variables stored on the stack), these values get copied into your function. If these are big variables, you program is now storing them twice. When your function is finished, you may need a way to return these values. Functions can generally only return one thing -- so what if you wanted to return two, three, or four things?

Pointers5

If you pass a pointer to your function, only the memory address gets copied (which is tiny). This saves your CPU a lot of work! Maybe your pointer points to a huge image array -- not only can your function work on the exact same data stored in the exact same memory location, but once it's done, there's no need to return anything. Neat!

You do have to be very careful though. Pointers can still go out of scope and be collected by the garbage collector. The values stored in memory, however, do not get collected. This is called a memory leak. You can no longer access the data (as the pointers have been destroyed), but it is still using up memory. This is a common reason for many programs to crash, and it can fail spectacularly if there is a large amount of data. Most of the time, your operating system will kill your program if you have a large leak (using more RAM than the system has), but that's not desirable.

Pointers2

Debugging pointers can be a nightmare, especially if you are working with large amounts of data, or working in loops. Their disadvantages and difficulty to understand are really worth the trade offs you gain in performance. Although remember, they may not always be required.

That's it for today. I hope you have learned something useful about a complex topic. Of course, we have not covered everything there is to know -- it's a very complex topic. If you are interested in learning more, I highly recommend C++ in 24 Hours.

If this was a bit complex, take a look at our guide to the easiest programming languages.

Did you learn how pointers work today? Have you got any tips and tricks you want to share with other programmers? Jump into the comments and share your thoughts below!