Pointers

CS 106B: Programming Abstractions

Autumn 2020, Stanford University Computer Science Department

Lecturers: Chris Gregg and Julie Zelenski

An image of a pointer breed of dog


Slide 2

Announcements

  • Assignment 4 is due end-of-day today (Friday). The grace period for submission is end-of-day Sunday.
  • Information about next week's midquarter assessment has been posted. If you have OAE accomodations and have not sent your OAE letter to the course staff, please do so as soon as you can.

Slide 3

Today's Goals:

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

Slide 4

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 5

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   1   2   3   4   5   6   7   8   9 
  • 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:  10   11   12   13   14   15   16   17   18   19 
  • 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
10
  • Based on the diagram above, what is the address of the pet variable?
    • It is 10.
    • 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 6

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:
10
1234
  • petPointer's value is a memory address.
  • What is a pointer? A memory address!

Slide 7

Introduction to Pointers

What is a pointer??

a memory address!

Slide 8

Introduction to Pointers

  • We generally use an arrow to "point" from a pointer to the address it points to:
petPointer:
10
1234
β†’
pet:
cat
10
  • 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 9

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 10

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 11

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!
    
  • 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
    
  • Now we have:
petPtr:
?
1234
β†’
?
    
pet:
cat
10
  • 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
10
  • 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 12

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
     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
10

Slide 13

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 14

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 15

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 16

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 17

Introduction to Pointers

What is a pointer??

a memory address!

Slide 18

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 19

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 20

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 21

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 22

Introduction to Pointers

What is a pointer??

a memory address!

Slide 23

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 24

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 25

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;
    }