Date Modified Tags Pointers / C / C++

Note: I have always heard that the best way to learn something, is to teach it to others. So this article is my own summary of what I am learning in my CS 161 class, written from the perspective of trying to teach it to someone. I do have some prior experience with C/C++ but I am definitely not trying to pass myself off as some kind of C/C++ super expert! :) If you see something blatantly wrong, then please contact me so I can get it fixed!

What is a pointer?

A pointer is a special type of variable that holds the memory address of another variable. It "points" to a variable.

Pointer Drawing{: .img-responsive .center-block }

Why use pointers?

That's a complex question that I am not sure I am fully qualfied to answer. However, I believe the reason pointers exist is because it makes C/C++ programs much more efficient and allows them to use less memory.

To understand why you have to understand the idea of passing a variable by reference. In many languages, when you pass by a variable reference you conceptualize it as passing that actual variable into the function (rather than a copy) and this means that the variable can be acted upon and changed in the function you pass it into. If you change the variable in that function, then you also change it in the calling function. This can be dangerous but it can also be valuable for conserving memory and for making permanent changes to variables other than just the one you a returning.

Now in C, everything is passed by value. There is no such thing in C as passing a variable by reference. Passing by reference is a protective abstraction that was added in later programming languages like C++ and Java. However there is a somewhat more simpler way of simulating passing by reference by using the memory address of a variable or pointer. Using a pointer is a type of indirection.

So, in C to "pass by reference" you pass the value of the pointer (which is a memory address) into the function which allows the function to make changes to the variable stored in that memory location by dereferencing the pointer.

Another big reason for using pointers is to help with dynamic memory allocation to provide a way to access nameless variables by using their memory address, stored in a pointer.

If you want a more eloquent description of a pointer then definitely see Chapter 5 of The C Programming Language by Brian Kerngighan and Dennis Ritchie.

Still, if you have never encountered pointers, then none of the above will make sense to you and it will not be helpful.

Some great resources that explain pointers better

Here is the start of David Malan's lecture on pointers from Harvard's CS50.



An old but great video from Stanford that describes how pointers work by using claymation.



This is another great video on pointers from a YouTube channel called mycodeshool

An example to better explain.

Ok, so let's first create an integer

int myInt = 100;

Now, let's create a pointer to that integer

int* ptr1 = &myInt; // & is the address of operator

Let's create another pointer with an alternate pointer declaration syntax

int *ptr2 = &myInt;

Let's create a third pointer with the final alternate pointer declaration syntax

int * ptr3 = &myInt;

As you can see, all of the above are equivalent and they declare a pointer variable named ptr that points to an int.

Now, why don't we print out the value of these pointers so we can see that they actually contain:

#include <iostream>
#include <string>
using namespace std;

int main()
{
    int myInt = 100;
    int* ptr1 = &myInt; // & is the address of operator
    int *ptr2 = &myInt;
    int * ptr3 = &myInt;

    cout << ptr1 << endl;
    cout << ptr2 << endl;
    cout << ptr3 << endl;

    return 0;
}

Output

0x7fe4e2d6060c
0x7fe4e2d6060c
0x7fe4e2d6060c

View and run code on cpp.sh

The numbers above represent the memory address in the computers memory where myInt is actually stored! So cool right!

So, what can we do with these addresses? We can use the addresses to access the variable that is actually stored at that address. To do that though, we need to specify to C++ that we want to get the value not of the pointer, but of the address it references.

Dereferencing with *

So far we have seen that * has two uses:

  1. It is the multiplication operator: 1 * 200

  2. And it can specify that a variable is a pointer: int* pointy;

Now there is actually a third use of *. Using * with a pointer variable in an expression, when you are not declaring it, tells C++ to go to the address that is stored in the pointer variable and get that value. This is called dereferencing the pointer.

Why is it called dereferencing?

Because one code snippet is worth 1000 paragraphs

// Example program
#include <iostream>
#include <string>
using namespace std;

int main()
{
    int myInt = 100;
    int* ptr1 = &myInt; // & is the address of operator
    int *ptr2 = &myInt;
    int * ptr3 = &myInt;

    cout << *ptr1 << endl;
    cout << *ptr2 << endl;
    cout << *ptr3 << endl;

    return 0;
}

Output

100
100
100

That is how you use a pointer to access the variable it points to.

& is not the same as &

So, just like * has multiple meanings, so too does & in C++.

  1. It can be used as the address of operator
  2. It can be used as the reference operator when passing a value into a function. Using the reference operator can be easier to use instead of passing the value of a pointer variable into a function because it provides some abstraction that hides some of the complexity of using pointers.

Anywhere outside of function parameters, the & is the "address of" opeator and not the reference operator.

Array names decay to pointers

Array names without brackets and a subscript decay to a pointer to the first element in the array.

#include <iostream>
using namespace std;

int main()
{

    int numArr[] = {100, 200, 300, 400, 500};

    cout << "The address of the first array element is: ";
    cout << numArr << endl;

    // using the dereferencing operator to get the element
    cout << "The first array element is: ";
    cout << *numArr << endl;

    return 0;
}

Output

The address of the first array element is: 0x7a54aeb1e280
The first array element is: 100

View and run code on cpp.sh

Pointer Arithmetic

You can use certain operators to perform some mathematical operations on pointer variables.

Example 1:

#include <iostream>
using namespace std;

int main()
{

    int numArr[] = {100, 200, 300, 400, 500};

    int* ptr1 = numArr;
    ptr1++;

    // now should point to the 2nd array element
    cout << *ptr1 << endl;

    ptr1--;

    // now should point to the first array element
    cout << *ptr1 << endl;

    return 0;
}

Output

200
100

View and run code on cpp.sh

You can use the ++, --, +=,-=,+,- operators to perform mathematical operations on pointers.

Example 2:

#include <iostream>
using namespace std;

int main()
{

    int numArr[] = {100, 200, 300, 400, 500};

    int* ptr1 = numArr;
    int* ptr2 = ptr1 + 1;
    int* ptr3 = ptr1 + 2;
    int* ptr4 = ptr1 + 3;

    cout << "ptr1 is: " << ptr1 << endl;
    cout << "ptr2 is: " << ptr2 << endl;
    cout << "ptr3 is: " << ptr3 << endl;
    cout << "ptr4 is: " << ptr4 << endl;



}

Output

ptr1 is: 0x767e538e9e40
ptr2 is: 0x767e538e9e44
ptr3 is: 0x767e538e9e48
ptr4 is: 0x767e538e9e4c

View and run code on cpp.sh

Using relational operators with pointer variables

You can also compare pointer variable values with relational operators. Elements that are later in the array have a greater value than earlier elements because their addresses are higher numbered.

#include <iostream>
using namespace std;

int main()
{

    int numArr[] = {100, 200, 300, 400, 500};

    int* ptr1 = numArr;
    int* ptr2 = ptr1 + 1;
    int* ptr3 = ptr1 + 2;
    int* ptr4 = ptr1 + 3;

    if (ptr4 > ptr1) cout << "True";

}

Output

True

View and run code on cpp.sh

Null and Null pointers

People are always asking, "What the heck is NULL and why should we use it?"

NULL is a named constant that is defined as 0. Using NULL though is said to make it more apparent that the 0 value if for a pointer type, rather than something like an int.

Pointers and dynamic memory allocation

Dynamically allocated memory is memory that is allocated at runtime and located on the heap (rather than the stack).

Why use dynamic memory allocation?

Dynamic memory is used when you don't know how many variables you need to declare up front.

If you have experience with newer programming languages than a lot of memory issues have likely been handled for you.

Stack vs Heap

If you want to work with dynamically allocated memory then it is helpful to know some the basics of the stack and the heap. Take a look at this amazing drawing I made to help you understand more!

Heap vs Stack{: .img-responsive .center-block }

Stack memory is where static and automatically allocated memory are stored.

Heap memory is a special section of memory, larger than the stack, that is used for dynamically allocated memory.

Notice also that the stack and heap are allocated towards each other. So that if you have too much heap or stack they can collide with each other and cause a memory overflow. This is why you may have heard of the terms stack and heap overflow.

Dynamic memory allocation is only possible in C/C++ through the use of pointers.

Dynamically allocated memory is nameless, which means we do not assign specific names to it because that would be impossible. Instead of using a named variable, we access the variable by using its memory address.

new operator

If you have some experience with C, then you may know about the malloc() function. malloc() stands for Memory Allocate and reutrns a pointer to a chunk of heap memory large enough to hold the number of bytes you specify. Typically, the size of operator is used with malloc():

int* newInt = malloc(sizeof(int));

C++ uses the new operator instead of functions like malloc().

Here is an example:

// Example program
#include <iostream>
#include <string>
using namespace std;

int main()
{

    int* ptr;  
    ptr = new int;

    cout << ptr << endl;
    return 0;
}

Output

0x19fcdf0 # memory address will probably be different.

View and run code on cpp.sh

The new operator in the code above asks the computer to allocate enough memory to hold a variable the size of an int and then it returns the address of that memory stored in the pointer variable ptr.

By using the dereferencing operator we can store values into the memory address.

*ptr = 1000;

We can print out those values in the same way.

cout << *ptr << endl;

Without dynamic memory allocation

If we didn't use dynamic memory allocation then we would have to know the exact number of integers the user may want to enter for the following program.

#include <iostream>
#include <string>
using namespace std;

int main()
{

    // for this program we have to know the max number of integers
    // the user might want to enter.

    int arrayOfInts[5];
    char userSel;

    int i = 0;
    while (userSel !=  'n')
    {
        cout << "Enter an int" << endl;
        cin >> arrayOfInts[i]; i++;

        cout << "Type 'y' to continue or 'n' to stop." << endl;
        cin >> userSel;            
    }

    return 0;
}

With dynamic memory allocation

The following program uses pointers, the new operator, and dynamic memory allocation to allow the user to enter an array that is only limited by the size of their heap memory.

#include <iostream>
using namespace std;

int main()
{
    int max = 3;   
    int* arrayOfInts = new int[max]; // allocated from heap
    int elmCnt = 0;
    char userSel;

    int i = 0;
    while (userSel !=  'n')
    {

        if (i >= max )
        {
            max = max * 2; // double the prev array size
            int* tempy = new int[max];

            for (int j = 0; j < i; j++)
            {
                tempy[j] = arrayOfInts[j]; // copy to new array
            }

            // now time to free the old array so it does not become a dangling
            // pointer
            delete [] arrayOfInts; // use [] when freeing an array

            arrayOfInts = tempy; // point arrayOfInts to the new array
        }

        cout << "Enter an int" << endl;
        cin >> arrayOfInts[i];
        elmCnt++;

        cout << "'y' to continue,'n' to stop." << endl;
        cin >> userSel;

        i++;          
    }

    delete [] arrayOfInts; // free the mem before termination

    return 0;
}

Comments

comments powered by Disqus