This is a quick review of pointers in C. If you are not an expert in pointers, I would strongly urge you to read everything on this page carefully. All the words here are carefully chosen to be as correct as possible. If there is anything you do not understand or wondering about the choice of words, please feel free to send an e-mail to the instructor. (If you see a bug on this page, please also let the instructor know.)

Just like integers and characters, a pointer is a primitive data type in C/C++. An integer takes up 4 bytes, a character takes up 1 byte, and a pointer takes up 4 bytes on a 32-bit machine (and 8 bytes on a 64-bit machine, but we will assume that we are running on a 32-bit machine for the rest of this discussion). A pointer is simply a 4-byte quantity that contains a memory address. Here's an example of how you would declare an integer variable, a character variable, and a pointer variable in C:
The first thing you need to get use to thinking about variables in this class is that a variable refers to an address, i.e., a memory location. We use variables to talk about memory!

In an assignment statement, the left-hand-side refers to a memory location that can store a value. The right-hand-side is evaluated to obtain a value and this value is put into the memory location referred to by the left-hand-side. When you see a variable somewhere on the right-hand-side of an assignment statement (or anywhere else), you need to see what's stored in the memory location referred to by that variable and use the value stored there.

In the above example, n refers to a memory location that can hold a 4-byte quantity. (Please note that the word "refer" has nothing to do with C++'s "reference variable".) By declaration, the compiler will interpret this memory location as containing an integer. Similarly, c refers to a memory location that can hold one byte of data. By declaration, the compiler will interpret this memory location as containing an character. Finally, p refers to a memory location that can hold a 4-byte quantity. Since it's declared (void*), the compiler just knows that it's a pointer, it can point to anything, i.e., it can contain a memory address of any object. (I'm using the word "object" to refer to anything in C. It can be an integer, a character, a data structure, a pointer, or even a pointer to a pointer of a pointer of a pointer.)

n = 5 means to put an integer value of 5 into the memory location referred to by n. c = 'z' means to put an encoded character 'z' into the memory location referred to by c. p = NULL means to put a value of 0 into the memory location referred to by p since NULL is simply defined as 0. We would call p a "NULL pointer" because it contains memory address zero. It is known that there is nothing at memory address zero. It's perfectly okay to point to it. But if you try to "see" what's in memory address zero (i.e., by dereferencing p), you will get a segmentation fault and your program will be killed by the operating system.

Please note that we often depict pointers by drawing arrows from a pointer type to another object. There are of course no arrows in memory. It would be more accurate to simply put a value of 0 into p in the above picture.

Continuing from the example above, let's see some assignments operations.
m = n means to evaluate the right-hand-side to get a numeric value and put the value into the memory location that's referred to by m. If you see a variable on the right-hand-side, you need to use the value at the memory location that's referred to by the variable. Similarly, p2 = p means to evaluate the right-hand-side to get a numeric value and put the value into the memory location that's referred to by p2. Since p is a variable on the right-hand-side, if you use the value at the memory location that's referred to by p, you would get 0. Therefore, you put 0 into p2. As a result, p2 will contain the same value as p (i.e., p2 will end up pointing to the same object as p).

On a 32-bit machine, the size of the address space (supposedly addressable memory locations) of a program is 232 bytes. An address is a number that can be used to refer to a memory location in the address space of a program. Therefore, an address is 4 bytes long on a 32-bit machine. The data type of a variable's address is a pointer data type. All pointer data types are compatible.

Since a variable refers to a memory location, you can actually get the memory location to which it refers:

&n denotes the address of n. Therefore, p3 = &n means to put the address of n into the memory location that's referred to by p3. The same address can also be put into the memory location that's referred to by p4 (pointer of a different type) since all pointer data types are compatible.

Please note that it's perfectly valid to do:

    int *p5 = (int*)0x12345678;
because it is possible to use 0x12345678 to refer to a memory location in the address space of a program. But if you try to dereference p5 and use what's in memory location 0x12345678, you need to make sure there's really something at location 0x12345678 or you will get a segmentation fault and your program will be killed by the operating system.

To dereference a pointer means to follow the pointer to where it points to and fetch what's there. For example:
First, n set to contain 23. m = *p4 means to evaluate the right-hand-side and put the value in m. If we have just p4 on the right-hand-side, the right-hand-side would evaluate to what's in p4. But we have *p4 instead (where * is the dereference operator). So, we need to follow the pointer to where it points to and fetch what's there. p4 points to n; therefore, *p4 evaluates to what was in n and you end up putting 23 into m.

Next, *p4 = 876 means to put a numeric value of 876 into the memory location referred to by *p4. Since p4 is a pointer, to get the memory location that's referred to by *p4, you dereference the p4 pointer by following the address stored at p4. As it turns out, you get the memory location referred to by n. So you end up putting 876 into n.

An array variable has the same type as a pointer. For example, if you have:
then a[0] and p5[0] refers to the same memory location, a[1] and p5[1] refers to the same memory location, and a[2] and p5[2] refers to the same memory location.

You need to be careful with pointer math. Although p5 evaluates to the address of variable a, the expression p5+1 will be calculated as p5+sizeof(int) (which is p5+4, which is the same as &a[1]) because p5 is declared as a pointer to int. Similarly, since p6 is declared as a pointer to short, the expression p6+1 will be calculated as p6+sizeof(short), which is p6+2, which would not correspond to any element of the a[] array.

Here's one weird thing about C. If a is an array, then a refers to the address of that array. Then what is &a? As it turns out, in C, if a is an array, &a is treated the same as a. Although an array has the same type as a pointer, this is how an array is different from a pointer.

In C++, there are "call by value", "call by pointer", and "call by reference" and it's all very confusing. In C, there is only one way to pass parameters in making a function call and that's using "call by value". This means that all function parameters are copied. For example, if you have:
    void foo(a, b, c)
    {
      ...
    }
and you call foo() by doing:
    foo(x, y, z)
effectively, it's like performing assignment operations (although in reality, it's a lot more complicated as you will learn in Ch 3 of the textbook):
    a = x;
    b = y;
    c = z;
no matter what the types of a, b, c, x, y, and z are.

Let's define a data structure:
    typedef struct tagFoo {
        int x;
        int y[3];
        char z[8];
    } Foo;

    static Foo foo;
The above says that the Foo data type/structure has 3 fields. The first field is referred to as x and it's a 4-byte integer. The 2nd field is referred to as y and it's an array of three 4-byte integers. The 3rd field is referred to as z and it's an array of eight one-byte characters. The size of a Foo data structure is therefore 4+12+8=24 bytes. The last line says that foo is an instance of the Foo data structure. Therefore, foo occupies 24 bytes of memory. Furthermore, you can use the "." operator to refer to different fields inside a data structure. Thus, foo.x refers to the first 4 bytes in foo (or zero byte into the foo data structure), foo.y refers to the next 12 bytes in foo (or 4 bytes into the foo data structure), and foo.z refers to the last 8 bytes in foo (or 16 bytes into the foo data structure).

When you do:

    Foo *foo_ptr=(Foo*)malloc(sizeof(Foo)):
you ask the memory allocator to allocate 24 contiguous bytes of memory from the heap and returns the first memory address of this block of data and store that address in the foo_ptr variable. Please understand that, colloquially, we say that foo_ptr is a "heap variable". Technically, that's incorrect. In the code above, foo_ptr is a local variable (a local pointer variable) that contains a heap address (or, "contains an address of a memory location that's in the heap segment of the address space").

When you then do:

    foo_ptr->y
It's simply a shorthand for:
    (*foo_ptr).y
(*foo_ptr), according to what I wrote above, means that you interpret what's stored in foo_ptr as an address and you dereference the pointer. In this case, when you dereference that pointer, you get the first address of the 24 bytes of memory that corresponds to the Foo data structure. Then you use the "." operator to access a field inside that data structure.

It's very important to understand when memory exists for all types of variables. There are 3 types of variable: local variable, dynamic variable, and global variable.

When you "create" a local variable, you are allocating memory "on the stack" (to be more specific, memory is allocated inside the "stack frame" that corresponds to the function in which the local variable is declared). We will discuss this in detail in Ch 3.

When you create a local variable, this type of memory allocation is called "automatic allocation". Memory is allocated automatically when the function is called and it's deallocated automatically when the function is returned. So, for "automatic memory allocation", the lifetime of a local variable is the lifetime of the function, i.e., the variable starts to exist at the beginning of the function; when this function calls other functions, this variable continues to exist (why? because you create new stack frames on top of the current stack frame); when the function returns, the variable stops existing (why? because you delete the current stack frame). This is something very important to understand.

What address do you get when you create a local variable? Well, it depends on where your current top of the stack is because when a function is called, a stack frame is created on top of the current stack frame. Then your local variables are automatically allocated inside that stack frame. Therefore, if you call the same function from different functions, the local variables may have differnt addresses.

For dynamically allocated memory, the lifetime of a dynamically allocated block of memory is different and you, the programmer, also gets to decide when it starts existing and when it stops existing. When you call malloc(), the block of memory starts to "exist". Then you can make function calls or return from function calls, the block of memory continues to "exist". When you call free(), the block of memory "stops existing". We will also discuss malloc() and free() in detail in Ch 3.

What address do you get when you call malloc()? Potentially, you can get a different address every time you call malloc(). If you free a block of memory that was returned from malloc(), you may get the same address next time you call malloc(), but then again, you may not. Dynamic memory is managed by a "memory allocator" which we will discuss in Ch 3.

Please understand that in both cases, when I said "stop existing", it does not mean that they disappear from the address space. It means that you must not assume that the content in those memory locations are unchanged. Once a variable "stops existing", the content of the memory location it refers to can change "randomly" (and that's why you shouldn't refer to them any more).

Finally, for global variables, they start existing when the program starts. They stop existing when the program is dead.

You, as the programmer, get to decide what type of variable to use. Therefore, it's extremely important to understand the lifetime of each type of variable.

A C function can only return a single value. What if you need to return multiple values? One way to do it is to define a data structure that can hold multiple values and you can dynamically allocate a data structure in the heap and return a pointer that points to this object.

Another way is to have the caller function sets up local variable to hold these return values and pass the addresses of these local variables as function arguments to receive return values. For example, let's say that you are reading a file that contains lines of text and each line contains 3 number (i.e., 3 strings separated by tabs where each string is an ASCII representation of an integer). It would be nice to write a function that will read the next line from the file, parse the line to get the 3 integers and return these integers. You would also need to know if reading from the file tailed or if parsing failed. So, your function may look like:

    int Get3Ints(...);
You can have this function return 0 to mean everything is fine and return (-1) if something is wrong. Let's further assume that we are using a global variable to represent the file so we don't have to pass that information into this function. How should this function return 3 integers?

The function that calls Get3Ints() can do something like the following:

    int first, second, third;

    if (Get3Ints(&first, &second, &third) == 0) {
        /* first, second, and third contains valid integers */
    } else 
        /* print error message and quit program, if appropriate */
    }
Then the function prototype of Get3Ints() would look like:
    int Get3Ints(int *p1, int *p2, int *p3);
As mentioned above, C function calls are "call by value". Therefore, when you are inside Get3Ints(), you should think of p1 as the address of the local variable named first in the caller function, p2 as the address of the local variable named second in the caller function, and p3 as the address of the local variable named third in the caller function. Therefore, the picture you should have in your head look like the following:
Inside Get3Ints(), when you need to set a return value, you would need to follow the pointer to set the return value. Therefore, your could would look like the following:
    int Get3Ints(int *p1, int *p2, int *p3)
    {
        int n1, n2, n3;
        /* read line from file, parse for 3 integers and store them in n1, n2, and n3 */
        if (/* all goes well */) {
            *p1 = n1;
            *p2 = n2;
            *p3 = n3;
            return 0;
        }
        return (-1);
    }
Some people likes to make their code compact. I think that's fine if you are an expert programmmer. But if you are new to C, I would strongly urge you to write your code in small steps to make it perfectly clear to you what your code is doing! In the above example, some would think that using n1, n2, and n3 would be a waste of space and time. My recommendation to you is that you don't need to worry about stuff like that! Correctness is much more important for this class. (When you become an expect programming, you can optimize your code then.) You should write super clear code so you know exactly what's going on in every line of your code. So, make sure you understand every line of the sample code above.

In the above example, it demonstrates that if a function argument is a pointer type, it does not necessarily have to point to a memoroy location in the heap! It's possible that it can point to a local variable (or even a global variable). It also demonstrates that if you want to call a function whose argument is a pointer type, you don't necessarily has to call malloc() and pass a pointer to the callee function! You can simply pass the address of a local (or global) variable since it's 100% compatible with a pointer type! You, the programmer, get to decide which way to go! (The trouble with malloc() is that once you are done with that piece of memory that you have allocated, you need to remember to call free() on it if you don't want to "leak memory". Therefore, you should use malloc() and free() pretty much only when you don't have a choice.) What's important is that the caller function and the callee function supposed to work together to get the job done. If you are writing the code for the caller function, you must know exactly what the callee function intends to use the function arguments. If you are writing the code for the callee function, you must make it super clear to anyone who will be writing the code for the call function what you intent to do with the function arguments. In the above example, the callee function (i.e., Get3Ints()) returns 3 integers to the memory locations pointed to by p1, p2, and p3, no matter if they point to the heap or somewhere else, and that would be a good way to write the code for a callee function!

Instead of returning 3 integers, what if I want to return 3 newly created objects (for example, Boats)? The description below parallels the example in the previous section. The main difference is that instead of (int), we have (Boat*) that points to a Boat which needs to be created/allocated and initialized. For example, I want a function called Get3Boats() that return 3 newly created and initialized boats, I might want to do something like the following:
    Boat *first_boat_ptr=NULL, *second_boat_ptr=NULL, *third_boat_ptr=NULL;

    if (Get3Boats(&first_boat_ptr, &second_boat_ptr, &third_boat_ptr) == 0) {
        /* first_boat_ptr, second_boat_ptr, and third_boat_ptr contains valid pointers to boats */
    } else 
        /* print error message and quit program, if appropriate */
    }
Then the function prototype of Get3Boats() would look like:
    int Get3Boats(Boat **pp1, Boat **pp2, Boat **pp3);
I need to use (Boat**), which has two asterisks, because the address of an object whose type is (Boat*) would be (Boat**)! You can think of (Boat**) as ((Boat*)*) and if you replace (Boat*) with (int), you basically have the code in the previous section!

Inside Get3Boats(), you might do something like the following:

    int Get3Boats(Boat **pp1, Boat **pp2, Boat **pp3)
    {
        Boat *p1 = (Boat*)malloc(sizeof(Boat));
        Boat *p2 = (Boat*)malloc(sizeof(Boat));
        Boat *p3 = (Boat*)malloc(sizeof(Boat));
        /* some code to initialize the boats pointed to by p1, p2, and p3 */
        if (/* all goes well */) {
            *pp1 = p1;
            *pp2 = p2;
            *pp3 = p3;
            return 0;
        }
        return (-1);
    }
You should contrast all this code with all the code in the previous section and notice that we are basically doing the same thing. An integer is an object, and a pointer to a boat is also an object, and this is how you can create objects in a function and have the function return it.

Shalow Copy vs. Deep Copy

One more twist! How about the following implementation?
    Boat first_boat, second_boat, third_boat;

    if (Get3Boats(&first_boat, &second_boat, &third_boat) == 0) {
        /* first_boat, second_boat, and third_boat contains valid boats */
    } else
        /* print error message and quit program, if appropriate */
    }
Inside Get3Boats(), you might do something like the following:
    int Get3Boats(Boat *p1, Boat *p2, Boat *p3)
    {
        Boat b1, b2, b3;
        /* some code to initialize the boats b1, b2, and b3 */
        if (/* all goes well */) {
            memcpy(p1, &b1; sizeof(Boat));
            memcpy(p2, &b2; sizeof(Boat));
            memcpy(p3, &b3; sizeof(Boat));
            return 0;
        }
        return (-1);
    }
You need to be extremely careful with code like this! If b1, b2, and b3 are very complex objects that contains pointers to other objects, using memcpy() to copy these inner pointers only copies the value of the pointers and not what they are pointing to and you need to think about the difference between copying a pointer and copying an object pointed to by a pointer! In C++, copying using memcpy() like the code above is called shallow copy. If shallow copy is all you need (because you know that the object you are dealing with is "flat" or you really know for sure that this works for your application), then this would be fine. Otherwise, don't write code like this! It's super important that you know exactly what you are dealing with and choose the right way to solve your problem. If you are interested in learning more about "shallow copy" vs. "deep copy" in C++, you can do a web search for "C++ shallow copy vs deep copy".