C:Pointers and References

From GPWiki
Jump to: navigation, search

Introduction

The ability to manipulate memory and memory locations directly is part of what makes C and C++ so powerful, so dangerous, and so difficult for beginners. C only has "pointers", but C++ uses pointers and "references" that have a nicer syntax and attempt to be safer. This article will attempt to demystify these concepts in the minds of beginning C/C++ programmers.

This article assumes the reader has a basic understanding of C/C++ including variable declaration, conditionals, loops, functions, and data types. This article will speak more specifically about C++.

Remember, pointers can twist your mind at first and they are a difficult concept to grasp. Often times it is hard to see where they would be useful.


The Basics

The "normal" way of storing information (data) in C++ is using certain predefined types like integers (int), floating point values (float, double), and characters (char). The programmer tells the compiler about the data he wishes to store by giving its type, its name, and possibly an initial value.

int   total_balls = 2300;
float gravity     = 9.8f;
char  start_char  = 'a';


None of this should be new or difficult. This is easy, assuming you know the different types, and the best part about it is that the compiler takes care of finding a place to store the variable and we just tell it when we want to know what is "stored" in the variable. But wait! The compiler knows where the variable is stored in memory, it has to. We can get at the address of a variable by using the address of operator, "&" (ampersand).

std::cout << &total_balls;
std::cout << &gravity;
std::cout << &start_char;


But why would we care about knowing the address of our variables? The compiler takes care of storing our data for us, right? Yes, and we as humans have very little use of printing out the addresses of variables except for debugging purposes. But we can store an address of a variable into a special data type called a pointer and the compiler takes care of storing our "pointer value" for us. We designate pointers with a data type (int, float...) followed by the * (asterisk) symbol.

int*   addr_balls = &total_balls;
float* addr_grav  = &gravity;
char*  addr_char  = &start_char;


Now we have variables that store the addresses of the other variables we already created! Not very useful yet. But these concepts are important, a pointer can point to practically anywhere in the computers memory, and wherever it is pointing to we can retrieve the value there or store a new value in that location.

Most pointers have a type associated with them, but there is a special pointer, a red-head in the group, called a void pointer (void*). Well, what's at the other end of the pointer, what is stored in the location? The programmer has to know that and cast it into an appropriate type.

Dereferencing and Multiple Pointers

If we have the address of an integer how do we get at the integer instead of the address. We use the * (asterisk) symbol again, which can be somewhat confusing.

// Declare an integer.
int total_balls = 205;
// Declare a pointer that "points" at the address of total_balls.
int* addr_balls = &total_balls;
// Print the address of total_balls twice.
std::cout << &total_balls << " " << addr_balls;
// Print out the value of total_balls twice.
std::cout << total_balls << " " << *addr_balls;


In this way we can switch between the memory address and the actual value stored there effortlessly. We can also have a pointer to another pointer, which requires two * (dereference) to obtain the object.

int a = 0;
int b = 1;
int* addr_int = &a;
int** addr_addr_int = &addr_int;
**addr_addr_int = 3; // changes variable "a" to 3
addr_int = &b;
**addr_addr_int = 3; // WOW, the same exact line now changes variable "b"


Practical Pointers

So far, we haven't really seen the use of pointers. Why do we need to know the address of a variable? There are many reasons including storing contiguous data (arrays), storing a "string" of characters (string), taking over memory management from the compiler, manipulating the same data from different functions, and not duplicating large chunks of memory when passing to a function in order to increase performance. Don't worry if these sound confusing now

Pointer Arithmetic

An array is a bunch of variables stored one after the other in memory so we can easily access any of the variables with a number. Instead of saying "Give me the book by Thomas Hardy" we can say "Give me book #3."

If we have a pointer to the first variable in an array, we can get or set the value of any other variable in the array. Since we know how big each object is we can "jump" to the correct address with some simple multiplication and addition. You've probably seen array[5] to get the sixth element in an array (remember array[0] is the first element). To calculate that location we move over 5 elements. Then we dereference that spot, to work with the object there. So array[5] == *(array + 5) is true. Notice how we dereferenced the address after we moved over five elements.

In fact it is hard to give a short example showing the usefulness of pointers since they mostly crop up in larger projects. But a character string is a good way to start.

// Something probably familiar:
char message[] = "Hello";
// But it's really sugary syntax for:
char message[] = {'H','e','l','l','o','\0'};


We have a pointer to a character and we know that after that character is another character, and after that another character until we reach the value 0. So we can print out a word, instead of just a character!

int index = 0;
while( *(message + index) != 0 )
{
    std::cout << *(message + index);
    ++index;
}
// alternatively
while( message[index] )
{
    std::cout << message[index];
    ++index;
}
// or simply
std::cout << message;


In that last example, the program doesn't do what you might expect it to do. It should print out a char* but instead, when you insert a char* into a output stream, it steps over every character until it reaches the null character (0) or ("\0").

Sources

  • Simple game using pointers: This source code is an attempt to provide an example of program where the use of pointers can be better understood.