Lecture 13 - Oct 5, 2023

Summary

In this lecture, we discuss dynamic memory allocation of arrays (of objects and of pointers to objects).

Last lecture

Destructors.

Today

Dynamic memory allocation of arrays of objects and pointers to objects.

Double pointers

Double pointers are variables that store an address to a pointer.

Example
int** p2p;
int *p, *q;

p = new int;
*p = 5;
p2p = &p;
q = *p2p; // *(&p) = p
*q = 8; // the new int

cout << **p2p << endl; // **(&p) = *p = the new int = 8

delete p; // frees up space pointed to by p
delete q; // double free
delete *p2p; // triple free

Remember the diagram from lecture 11.

diagrams/lecture11-diagram3.svg

Dynamic memory allocation of arrays

diagrams/lecture13-diagram1.svg

int a[4] = {1,2,3,4}

a is an alias/nickname that has address to the first element in the array.

a      <-> &a[0]
*a     <-> a[0
*(a+1) <-> a[1]

There are different ways to have an array that stores data.

Fixed-size array

int arr[4]; // fixed number

This will be allocated on the stack.

Variable-size array

int size;
cin >> size;
int arr[size]; // variable number

This will be allocated on the stack too.

Dynamically allocate memory for the array

int size = 7;
int* arr = new int[size]; // dynamically allocate memory

This array will be allocated on the heap.

Advice: The programmer can “control” the lifetime of the array in the memory by freeing the memory anytime with delete.

Example
delete [] arr;
arr = NULL;

Can we dynamically allocate an array of pointers to integers? Yes.

diagrams/lecture13-diagram2.svg

int** arr2p = new int*[4];

for (int i = 0; i < 4; i++) {
    arr2p[i] = new int;
}

diagrams/lecture13-diagram3.svg

for (int i = 0; i < 4; i++) {
    *arr2p[i] = i + 1;
}

diagrams/lecture13-diagram4.svg

for (int i = 0; i < 4; i++) {
    delete arr2p[i];
    arr2p[i] = NULL;
}

diagrams/lecture13-diagram5.svg

delete [] arr2p;
arr2p = NULL; // arr2p -> NULL

Can we dynamically allocate an array of objects? Yes.

class Student {
    public:
        string name;
        int ID;
        Student() { ID = 0; name = ""; }
        ~Student() { cout << "Destructor" << endl; }
}

int main() {
    // Default constructor called 3 times
    Student* arr = new Student[3];
    
    // Destructor called 3 times
    // no destructor will be called without delete
    delete [] arr;
    arr = NULL;

    return 0;
}

Can I have an array of pointers to objects? Yes

diagrams/lecture13-diagram6.svg

int main() {
    // no constructors called
    Student** arr2p = new Student*[3];

...

diagrams/lecture13-diagram7.svg

...

    for (int i = 0; i < 3; i++) {
        arr2p[i] = new Student;
    }

...

diagrams/lecture13-diagram6.svg

...

    for (int i = 0; i < 3; i++) { 
        arr2p[i]->ID = i + 1;
    }
...

diagrams/lecture13-diagram8.svg

...

    for (int i = 0; i < 3; i++) { 
        delete arr2p[i];
    }

...

diagrams/lecture13-diagram9.svg

...

    delete [] arr2p;
    arr2p = NULL;
}

Read at home: Recap on Memory Management Issues

Losing access to a dynamically allocated space

int *p = new int;
*p = 100;
p = new int; // memory leak

diagrams/lecture13-diagram10.svg

Solution: Have a delete before making p point to a new memory space.

int *p = new int;
*p = 100;
delete p;
p = new int;

Access a freed memory space

int *p = new int;
*p = 100;
delete p;
*p = 3; // challenging to debug

Solution: It is a good practice to set the pointer to NULL or nullptr after delete.

Dereference an undefined pointer

int *p;
int y;
y = *p; // p has garbage address
                // defeferencing will access a garbage address

Double free

int *p = new int;
delete p;
...
delete p; // double free

Delete a variable stored on the stack

int x = 4;
int *p;
p = &x;
delete p; // x is on stack, you can't delete it

Type mismatch assignment

int** <- int*,
int* <- int**,
int** <- int,
int* <- int
int *p, *q;
int **pp;

// pp: int**
// p: int*
pp = p; // type mismatch int** <- int*

// *p: p
pp = *p; // type mismatch int** <- int

p = pp; // type mismatch int* <- int**