Pointers

CS 106B: Programming Abstractions

Spring 2023, Stanford University Computer Science Department

Lecturer: Chris Gregg, Head CA: Neel Kishnani

An image of a pointer breed of dog


Slide 2

CS198 Pitch


Slide 3

Announcements

  • Assignment 4 is due next Friday.

Slide 4

Why do we care about delete?

  • Now that we know a bit about pointers and memory management, we might wonder why delete is important.
  • Take a look at the following code:
const int INIT_CAPACITY = 1000000;

class Demo {
public:
    Demo(); // constructor
    string at(int i);
private:
    string *bigArray;
};

Demo::Demo()
{
    bigArray = new string[INIT_CAPACITY];
    for (int i = 0; i < INIT_CAPACITY; i++) {
        bigArray[i] = "Lalalalalalalalala!";
    }
}

string Demo::at(int i)
{
    return bigArray[i];
}
  • If you look at the Demo::Demo() function – that is a lot of strings!
    • 1MB array * (20 chars + ~30 bytes of overhead for each string) = 50MB per class instance
  • If we then have this main function:
int main()
{
    for (int i=0;i<10000;i++){
        Demo demo;
        cout << i << ": " 
             << demo.at(1234) 
             << endl;
    }
    return 0;
}
  • We are now creating 10,000 instances of our 50MB class…we will use 2GB of memory!
  • Let's see what will happen! An image of a terminal icon for accessing the command-line terminal on a Mac

Slide 5

What happened?

  • The program slowed to a crawl! We used lots of memory, and kept it!
  • We call this problem a memory leak, and we want to avoid it. In other words: return the memory you don't need when you're done with it!
  • Even though each of the 10,000 class instances went out of scope, we kept all the memory, because it was dynamically allocated. Our computer started running out of memory!
  • We requested the memory (with new) in the constructor.
  • When should we return the memory (with delete)?
    • We want to wait until the part of our program that uses our class is done with it.
    • In many cases, this is when the class instance goes out of scope.
    • When a class instance goes out of scope, the class can clean up for itself (i.e., return all of its allocated memory) in the "destructor." (not the deconstructor…).
    • The destructor is automatically called by the runtime when a class instance goes out of scope.
    • A destructor is defined with a funky syntax: ClassName::~ClassName().

Here is the destructor we need for our program:

Demo::~Demo()
{
    delete[] bigArray;    
}
  • Now, every time our class instance goes out of scope, we return the memory to the operating system, so we never have more than 50MB used at once. No more slowing down!

Slide 6

Today's Goals:

  • Introduction to Pointers
    • What are pointers?
    • Pointer Syntax
    • Pointer Tips
    • Pointer Practice
    • Binky
    • Back to classes
    • The copy constructor

Slide 7

Introduction to Pointers

  • The next major topic is about the idea of a pointer in C++. We need to use pointers when we create data structures like vectors and linked lists (which we will do next week!)
  • Pointers are used heavily in the C language, and also in C++, though we haven't needed them yet (we briefly saw them last time when we introduced dynamic memory)
  • Pointers delve under the hood of C++ to the memory system, and so we must start to become familiar with how memory works in a computer.

Slide 8

Your computer's memory system

  • The memory in a computer can be thought of simply as a long row of boxes, with each box having a value in it, and an index associated with it.
  • If this sounds like an array, it's because it is!
  • Computer memory (particularly, Random Access Memory, or RAM) is just a giant array. The "boxes" can hold different types, but the numbers associated with each box is just a number, one after the other:
values (ints): 7 2 8 3 14 99 -6 3 45 11
index:  0   4   8   12   16   20   24   28   32   36 
  • In C++, the location of each box is the address of the box: the address can tell us where the variable is located (like a house address).
values (strings): cat dog apple tree shoe hand chair light cup toe
address:  100   200   300   400   500   600   700   800   900   1000 
  • Let's create string variable, called pet, with a value of "cat":
string pet = "cat";
  • We can now refer to a particular value by its address. We can imagine it like this, based on the array above:
pet:
cat
100
  • Based on the diagram above, what is the address of the pet variable?
    • It is 100.
    • The operating system determines the address, not you! In this case it happens to be 10 (according to our diagram), but it could be any value.

Slide 9

A pointer is just a memory address

  • At this point, we have a variable, and we have said that the value has an address.
  • If we store that memory address in a different variable, it is called a pointer.
string pet = "cat";
pet:
cat
10
  • Let's say we have another variable, called petPointer that looks like this:
petPointer:
100
1234
  • petPointer's value is a memory address.
  • What is a pointer? A memory address!

Slide 10

Introduction to Pointers

What is a pointer??

a memory address!

Slide 11

Introduction to Pointers

  • We generally use an arrow to "point" from a pointer to the address it points to:
petPointer:
100
1234
β†’
pet:
cat
100
  • We really don't care about the actual memory address numbers themselves, and most often we will simply use the visual "pointer" arrow to show that a variable points to another variable, without the numbers:
petPointer:
 
β†’
pet:
cat

Slide 12

What you need to know about pointers

  • Every location in memory, and therefore every variable, has an address.
  • Every address corresponds to a unique location in memory.
  • The computer knows the address of every variable in your program.
  • Given a memory address, the computer can find out what value is stored at that location.
  • While addresses are just numbers, C++ treats them as a separate type. This allows the compiler to catch cases where you accidentally assign a pointer to a numeric variable and vice versa (which is almost always an error).

Slide 13

Pointer Syntax

  • Pointer syntax can get tricky. We will not go too deep – you'll get that when you take cs107!

  • Pointer Syntax #1: To declare a pointer of a particular type, use the * (asterisk) symbol:
    string* petPtr;  // declare a pointer (which will hold a memory address) to a string
    int* agePtr;     // declare a pointer to an int
    char* letterPtr; // declare a pointer to a char
    
  • The type for petPtr is a string* and not a string. This is important! A pointer type is distinct from the pointee type.

Slide 14

Pointer Syntax

  • Pointer Syntax #2: To get the address of another variable, use the & (ampersand) symbol. This is not a reference! It is the same symbol, but does not mean the same thing.
    string* petPtr; // declare a pointer (which will hold a memory address) to a string
                    // at this point, petPtr's value is bogus!
                    // petPtr does have its own address, which we
                    // will pretend is 1234
    
  • So far, here is what we have:
petPtr:
?
1234
β†’
?
  • When we declare a pointer, its value is bogus! It doesn't have anything to point to yet, so the value isn't anything useful.
  • Now, let's create a pet variable:
    string pet = "cat"; // a string variable, pretend it is at memory location 10
    
  • Now we have:
petPtr:
?
1234
β†’
?
    
pet:
cat
100
  • In order to get petPtr to point to pet, we have to assign the address of pet to petPtr. We do this like any other assignment, except that we use the & symbol:
    petPtr = &pet; // petPtr now holds the address of pet
    
  • Finally, we have this situation:
petPtr:
10
1234
β†’
pet:
cat
100
  • Notice that petPtr's value is 10, which is the address of pet. That's because petPtr is a pointer, which means that its value is an address!
  • By the way: you almost never need to do this in CS106B! You'll use it more in CS 107, but if you find yourself using it in this class, double-check your reasons.
  • Let's look at this example in Qt Creator. Logo for the Qt Creator IDE

Slide 15

Pointer Syntax

  • Pointer Syntax #3: To get value of the variable a pointer points to, use the * (asterisk) character (in a different way than before!):
     string* petPtr; // declare a pointer to a string
     string pet = "cat"; // a string variable, pretend it is at memory location 10
     petPtr = &pet; // petPtr now holds the address of pet
     cout << *petPtr << endl; // prints out "cat"
    
  • When we use * in this way, we say that we are dereferencing the pointer, which follows the address to its location and gets what is at that location.
petPtr:
10
1234
β†’
pet:
cat
100

Slide 16

An important note: pointers are just numbers!!!!!

  • Throughout the history of CS106B, students have been confused by pointers. There is nothing magical about them!
  • Pointers are just numbers that are associated with a type. We rarely care what those numbers actually are, but they are just numbers.
  • You can assign a pointer to another pointer of the same type, and you're just using integers:
int x = 4; // pretend x has some address, which is a number
int y;
y = x; // what is y's value? It's 4;

cout << "x: " << x << ", y:" << y << endl;

int *xPtr;
int *yPtr;

xPtr = &x;   // what is xPtr's value? It is the address of x, some number
yPtr = xPtr; // what is yPtr's value? It is also the address of x, the same number

// we need to cast to a size_t below so we print out a regular number
cout << "xPtr: " << (size_t)xPtr << ", yPtr:" << (size_t)yPtr << endl;

Output:

x: 4, y:4
xPtr: 123145559043436, yPtr:123145559043436

(each time we run the program, we might get a different output for the second line above, but the two numbers will always be the same)


Slide 17

Pointer tips

  • Pointer Tip #1: To ensure that we can tell if a pointer has a valid address or not, set your declared pointer to nullptr, which means "no valid address" (it actually is just 0 in C++).
    • Instead of this:
      string* petPtr; // declare a pointer to a string with no defined value
      
petPtr:
?
1234
β†’
?
  • We do this, instead:
     string* petPtr = nullptr; // declare a pointer to a string that points to nullptr
    
petPtr:
0
1234
β†’
nullptr (not a valid address)

Slide 18

Pointer tips

  • Pointer Tip #2: If you are unsure if your pointer holds a valid address, you should check for nullptr
    void printPetName(string* petPtr) {
        if (petPtr != nullptr) {
            cout << *petPtr << endl; // prints out the value pointed to by petPtr
                                     // if it is not nullptr
        } else {
            cout << "petPtr is not valid!" << endl;
        }
    }
    

Slide 19

Pointer practice

  • The little boxes we draw to show the memory are so, so important to understanding what is happening. Always draw boxes when learning pointers!
    int* nPtr = nullptr;
    
  • What type does this pointer point to?
  • What should we draw?
  • Answers:
  • The type that the pointer points to is an int – in other words, if we follow the pointer's value (which is an address!), we will find an int.
  • We should draw the following:
nPtr:
0
7224
β†’
nullptr
  • Where did the 7224 come from?
    • The operating system determines what that value is!
    • I just made up the value for this example, and you will almost never care what the address of the pointer itself is (in this class, anyway – you will care about that in CS 107!). The 7224 just tells us where nPtr is located, and we don't care!

Slide 20

Pointer Practice

  • Let's continue the previous example:
    int* nPtr = nullptr;
    int n = 16;
    
  • What should we draw?
    • We have two variables, nPtr and n. They are not related to each other in any way yet:
nPtr:
0
7224
β†’
nullptr
n:
16
5432
  • I made up the number 5432 because we don't know (or care) what it is.

  • Let's add one more line:
    int* nPtr = nullptr;
    int n = 16;
    nPtr = &n;
    
  • What should we do now?
    • We update the value of nPtr to the address of n:
nPtr:
5432
7224
β†’
n:
16
5432
  • We now say that nPtr points to n.

Slide 21

Introduction to Pointers

What is a pointer??

a memory address!

Slide 22

Pointer Practice

  • Let's look at the following code, and draw our pictures:
    string* sPtr = nullptr;
    string s = "hello";
    sPtr = &s;
    cout << *sPtr << endl;
    
  • After the first two lines, this is what we have:
sPtr:
0
6420
β†’
nullptr
s:
hello
9988
  • After the third line, we have:
sPtr:
9988
6420
β†’
s:
hello
9988
  • The last line prints out:
    hello
    

Slide 23

Pointer practice

  • What is the output of the following?
    string* sPtr = nullptr;
    string s = "hello";
    cout << *sPtr << endl;
    
  • Answer: A segmentation fault message that says the following: *** *** STANFORD C++ LIBRARY *** A segmentation fault (SIGSEGV) occurred during program execution.  *** This typically happens when you try to dereference a pointer *** that is NULL or invalid.  *** *** Stack trace (line numbers are approximate): *** string:1500              string::__get_pointer() const *** string:1228              string::data() const *** ostream:1047             ostream& operator<<(ostream&, const string&) *** VideoCompression.cpp:33  main() *** *** To learn more about the crash, we strongly *** suggest running your program under the debugger.

  • This is what we have in memory:
sPtr:
0
6420
β†’
nullptr
s:
hello
9988
  • When you dereference a nullptr, you seg fault!

Slide 24

Pointer Practice

  • You can also use the dereferencing operator to set the value of the "pointee" (the variable being pointed to):
    string* sPtr = nullptr;
    string s = "hello";
    sPtr = &s;
    *sPtr = "goodbye";
    cout << s << endl;
    
  • This is what we have in memory after the first three lines:
sPtr:
9988
6420
β†’
s:
hello
9988
  • Then, the third line changes the picture to this:
sPtr:
9988
6420
β†’
s:
goodbye
9988
  • And this is the output:
    goodbye
    

Slide 25

Pointer Practice

  • If you set one pointer equal to another pointer, they both point to the same variable!
    string* sPtr1 = nullptr;
    string* sPtr2 = nullptr;
    string s = "hello";
    sPtr1 = &s;
    cout << *sPtr1 << endl;
      
    sPtr2 = sPtr1;
    cout << *sPtr2 << endl;
    
  • After the pointers and string are created:
sPtr1:
0
6420
β†’
nullptr
sPtr2:
0
2232
β†’
nullptr
s:
hello
9988
  • Next, we set sPtr1 to point to s, and print it out:
sPtr1:
9988
6420
β†’
s:
hello
9988
sPtr2:
0
2232
β†’
nullptr
  • Output:
    hello
    
  • The last two lines (repeated here) produce the following:
    sPtr2 = sPtr1;
    cout << *sPtr2 << endl;
    
sPtr1:
9988
6420
β†’
s:
hello
9988
sPtr2:
9988
2232
β†—οΈŽ
  • Output:
    hello
    
  • Notice that both pointers have the same value, 9988, which is the address of s, and therefore, they both point to s.

  • Let's keep going with this example, with three more lines of code:
    *sPtr1 = "goodbye";
    cout << *sPtr1 << endl;
    cout << *sPtr2 << endl; 
    
  • This code dereferences sPtr1, goes to the location it points to, and sets the value of that string to goodbye. What do we have now?
sPtr1:
9988
6420
β†’
s:
goodbye
9988
sPtr2:
9988
2232
β†—οΈŽ
  • Because both pointers point to the same location, they both end up print out the same thing!
    goodbye
    goodbye
    

Slide 26

Introduction to Pointers

What is a pointer??

a memory address!

Slide 27

More information about addresses

  • Addresses are just numbers, as we have seen. However, you will often see an address listed like this:
    0x7fff3889b4b4 
    

    or this:

    0x602a10
    
  • This is a base-16, hexadecimal representation. The 0x just means "the following number is in hexadecimal notation."
  • The letters are used because base 16 needs 16 digits:
    0 1 2 3 4 5 6 7 8 9 a b c d e f
    
  • This is a base-16, or "hexadecimal" representation. The 0x just means "the following number is in hexadecimal."
  • The letters are used because base 16 needs 16 digits: 0 1 2 3 4 5 6 7 8 9 a b c d e f

  • So, you might see the following – remember, we don't care about address values, just that they are memory locations:
sPtr1:
0xfcab0
0xfe01a
β†’
s:
goodbye
0xfcab0
sPtr2:
0xfcab0
0xfe5c2
β†—οΈŽ

Slide 28

Binky

  • In 1999 (!), a certain Nick Parlante produced a little stop-animation video, called Pointer Fun with Binky
  • Here it is in all its glory:

Slide 29

If we have time: Copy Constructors

An image of a rectangle encasing the qt logo

  • Let's take a look at a simple Rectangle class to demonstrate a problem that we need to be aware of.
  • Here is the header file, rectangle.h, for the program:
    #pragma once
      
    class Rectangle {
    public:
        Rectangle(double width = 1, double height = 1); // constructor
        ~Rectangle(); // destructor
          
        double area();
        double perimeter();
        double getHeight();
        double getWidth();
          
    private:
        double *height; // pointer to a double
        double *width;  // pointer to a double
    };
    
  • Here is the rectangle.cpp file:
    #include "rectangle.h"
      
    Rectangle::Rectangle(double width, double height) { // constructor
        this->width = new double;
        this->height = new double;
        *(this->width) = width;
        *(this->width) = height;
    }
      
    Rectangle::~Rectangle() { // destructor
        delete height;
        delete width;
    }
      
    double Rectangle::area() {
        return *width * *height;
    }
      
    double Rectangle::perimeter() {
        return 2 * *width + 2 * *height;
    }
      
    double Rectangle::getHeight() {
        return *height;
    }
      
    double Rectangle::getWidth() {
        return *width;
    }
    
  • Here is the main function for our program:
    int main() {
        Rectangle r(3,4);
        cout << "Width: " << r.getWidth() << ", ";
        cout << "Height: " << r.getHeight() << endl;
          
        cout << "Area: " << r.area() << endl;
        cout << "Perimeter: " << r.perimeter() << endl;
          
        // let's make a copy:
        Rectangle r2 = r; // THIS CRASHES!!!
          
        return 0;
    }
    
  • What happened? Why did we crash?
    • We need to dig under the hood
  • Let's look at what the following two lines do:
    Rectangle r(3,4);
    Rectangle r2 = r;
    
  • The two private variables we have are int* pointers, width and height. Becuase we use new to allocate space for the data for those pointers, we don't have variables with names for the data, but the data is still in memory. This is r:
r.width:
0x99
0x61
β†’
3
0x99
r.height:
0x9f
0x63
β†’
4
0x9f
  • When you make an assignment of one class instance to another in C++, the default thing to do is to construct the class and simply copy over the data in the class variables. That would be width and height, which are pointers.
    • That means that what gets copied are two addressese width and height, which are pointers.
    • That means that what gets copied are two addresses. Below, we have r on the left, and r2 on the right:
r.width:
0x99
0x61
β†’
3
0x99
←
r2.width:
0x99
0x65
r.height:
0x9f
0x63
β†’
4
0x9f
←
r2.height:
0x9f
0x67
  • Notice that both r.width and r2.width point to the same pointee, and likewise for r.height and r2.height. This is bad news! Now, when the destructor gets called, both will try to delete the value that was allocated, and this isn't allowed! The program crashes!
  • What do we do? We define a copy constructor that tells the compiler how to copy our class. Without it, it will simply copy the values in the class variables, which isn't what we want in this case.
  • Here is the copy constructor definition in rectangle.h:
    class Rectangle {
    public:
        Rectangle(double height = 1, double width = 1); // constructor
        Rectangle(const Rectangle &src); // copy constructor
    ...
    
  • Here is the copy constructor itself. We need to allocate new memory for the new data, then we assign the old data to our new copy. This goes into rectangle.cpp:
    Rectangle::Rectangle(const Rectangle &src) { // copy constructor
        width = new double; // request new memory
        height = new double;
          
        // copy the values
        *width = *src.width;
        *height = *src.height;
    }