Source: CS 225 Midterm 1 Solutions, UIUC
Tags: pointers, references, address-of operator, dereference, dynamic memory, destructor, copy constructor, assignment operator, Big Three, Rule of Three, deep copy, shallow copy, C++, const correctness, int**, double pointer
Difficulty: Intermediate Prerequisites: Basic C++ syntax, understanding of variables and functions, familiarity with classes and objects.
This material covers how C++ manages memory at a low level: pointers, references, the address-of operator, and what happens when you allocate memory on the heap with new. It then builds on that foundation to explain why classes that own dynamic memory need custom destructors, copy constructors, and assignment operators (the "Big Three"). If you are coming in cold, make sure you understand the difference between stack and heap memory first. This is one of the most commonly tested areas in CS 225 and is foundational to everything that follows in the course.
Pointers store addresses, and you must point them at valid memory before dereferencing. When a class allocates heap memory, you need to write your own destructor, copy constructor, and assignment operator to avoid memory leaks and dangling pointers. The copy constructor must perform a deep copy: allocate new memory and copy the contents, rather than just copying the pointer values.
Pointer
A variable that stores the memory address of another variable. Declared with , e.g. int p;. You access the value at that address by dereferencing with *p. In simple terms, a pointer is a signpost that says "the data you want is over there."
Address-of operator (&)
Returns the memory address of a variable. If x is an int, then &x is of type int* and holds the address where x lives in memory.
Dereference operator (*)
Accesses the value stored at the address a pointer holds. If p points to x, then *p gives you the value of x.
Dynamic memory allocation
Memory allocated at runtime on the heap using new. It persists until explicitly freed with delete. Stack memory, by contrast, is automatically reclaimed when a function returns. Think of it as renting storage space: you have to hand it back yourself, or it stays occupied forever (memory leak).
Deep copy
Copying an object by allocating new memory and duplicating the actual data, so the copy and original are fully independent. Changing one does not affect the other.
Shallow copy
Copying only the pointer values, so both the original and the copy point to the same underlying memory. The default copy constructor performs a shallow copy, which is dangerous when the class owns heap memory.
The Big Three (Rule of Three)
If a class allocates dynamic memory, you must write all three of: a destructor, a copy constructor, and an assignment operator. If you need one, you almost certainly need all three. In simple terms, if your class uses new, you need custom versions of all three to avoid leaks and crashes.
Destructor
A special member function (~ClassName()) called automatically when an object goes out of scope or is deleted. Its job is to release any dynamically allocated memory the object owns.
Copy constructor
A constructor that creates a new object as a copy of an existing one. Signature: ClassName(const ClassName & source). Must perform a deep copy when the class owns dynamic memory.
const member function
A member function declared with const at the end of its signature, e.g. void foo() const;. This promises the function will not modify any member variables of the object. The compiler enforces this.
int** (double pointer)
A pointer to a pointer to an int. Can represent several things once initialised: a dynamic array of int pointers, a pointer to a dynamic array of ints, or a dynamic 2D array of ints. All three interpretations are valid for the same declaration.
Before you dereference a pointer, it must point at something valid.
y = &x; makes y point to x. After this, (*y)++ increments x through the pointer.
y = x; is a type error when y is int* and x is int, because you would be assigning an integer value to a pointer variable.
*y = x; dereferences an uninitialised pointer, which is undefined behaviour (likely a crash).
y = &x; is a type mismatch: y is an int, but &x is an int*.
When a function takes int * x, it receives a copy of the pointer, not the pointer itself.
Inside the function, reassigning x = y; only changes the local copy of the pointer. The caller's pointer (or variable) is unaffected.
To modify what the pointer points to, you dereference: *x = 16; would change the caller's data. But reassigning the pointer itself does nothing to the caller.
In the exam example, myFun(&i) passes the address of i, but the function reassigns its local pointer x to point elsewhere, then deletes that other memory. The value of i remains 9.
You need a custom destructor when your class has allocated dynamic memory (used new in its constructors or member functions).
Having pointer members alone is not sufficient reason. A pointer might point to memory the class does not own.
Having array members (e.g. int arr[10];) does not require a custom destructor, because stack-allocated arrays are cleaned up automatically.
const Keyword on Member FunctionsMarking a function const means it cannot modify the object's member variables.
If setSameRadius is declared const but tries to assign radius = orig.radius;, it will not compile. The function is promising not to change this, but the assignment violates that promise.
This is a compile-time error, caught before the programme ever runs.
int**int ** myVar; declares a pointer to a pointer to int.
Once initialised, this could be used as:
A dynamic array of int* values (array of pointers): myVar = new int*[n];
A pointer to a dynamic array of ints: int* arr = new int[n]; myVar = &arr;
A dynamic 2D array: allocate an array of int*, each pointing to its own int array.
All three interpretations are valid for the same type.
The Slideshow class has three members:
Image** slides; (dynamic array of Image pointers)
int* durations; (dynamic array of ints)
int slidecount;
The copy constructor must:
Copy the slidecount value from the source.
Allocate a new Image* array of size slidecount for slides.
Allocate a new int array of size slidecount for durations.
Loop through each index and copy the duration values.
For each slide pointer, check if it is NULL:
If non-NULL, allocate a new Image using the copy constructor: new Image(*(source.slides[j])).
If NULL, set the corresponding pointer to NULL.
Slideshow::Slideshow(const Slideshow & origval) {
slidecount = origval.slidecount;
slides = new Image *[slidecount];
durations = new int[slidecount];
for (int j = 0; j < slidecount; j++) {
durations[j] = origval.durations[j];
if (origval.slides[j] != NULL)
slides[j] = new Image(*(origval.slides[j]));
else
slides[j] = NULL;
}
}
Key points in this solution:
New memory is allocated for both arrays (deep copy, not pointer aliasing).
Each Image object is copied using its own copy constructor, not just the pointer.
NULL pointers are handled explicitly to avoid dereferencing NULL.
Deep vs. shallow copy matters everywhere objects own resources. A photo editor that duplicates a layer, a game engine that clones entities, a database connection pool that copies configuration objects: all of these need deep-copy semantics to avoid one object's destruction corrupting another's state.
Students often think having a pointer member automatically means you need a custom destructor. You only need one if your class allocated the memory that pointer points to (i.e. used new).
Students frequently confuse reassigning a local pointer parameter with modifying the caller's data. Changing where a local pointer points does nothing to the caller. You must dereference to modify the underlying data.
Students sometimes write copy constructors that copy pointer values instead of allocating new memory. This creates two objects sharing the same heap memory, and deleting one will corrupt the other.
Forgetting to handle NULL pointers in a copy constructor is a common source of segfaults on exams.
Pointer mechanics (address-of, dereference, pass-by-pointer) appear as multiple-choice questions in nearly every CS 225 midterm.
The Big Three copy constructor is a perennial 20-point written problem. Expect to write one from a class definition.
const correctness questions test whether you understand compile-time vs. runtime errors.
Graders award points for comments in code that show you understand the deep copy and NULL-handling logic, not just for working code.
True or false: int p; p = 5; is safe C++ code.
Fill in the blank: To make pointer y point to variable x, you write y = ______;.
True or false: The default copy constructor performs a deep copy of dynamically allocated memory.
Fill in the blank: The Big Three are the destructor, the copy constructor, and the ______.
True or false: A const member function can modify the object's member variables.
Answers: 1. False (p is uninitialised). 2. &x. 3. False (it performs a shallow copy). 4. Assignment operator. 5. False.
Q: Given int x = 10; int p = &x; (p)++;, what is the value of x?
A: 11. The pointer p points to x, and (*p)++ increments the value at that address.
Q: Why does the copy constructor need to check for NULL pointers before copying Image objects in the Slideshow example?
A: Because dereferencing a NULL pointer (to call the Image copy constructor) would cause a segmentation fault. If a slide pointer is NULL, you simply set the corresponding pointer in the new object to NULL.
Q: A class has int* data; as a member and uses new int[100] in its constructor. Do you need a custom destructor? Why?
A: Yes. The class allocated dynamic memory with new, so the destructor must call delete[] data; to free it. The default destructor would only destroy the pointer variable itself, leaking the heap memory.
Q: What is wrong with a copy constructor that does slides = source.slides; instead of allocating new memory?
A: This is a shallow copy. Both the original and the copy now point to the same array in memory. When one object's destructor deletes that array, the other object is left with a dangling pointer, leading to undefined behaviour.
Q: In a function void foo(int *x), does x = new int; inside the function change the caller's pointer?
A: No. x is a local copy of the pointer. Reassigning it only changes the local copy. The caller's original pointer still points where it did before the call.
This material connects directly to linked lists and iterators later in CS 225, because linked list nodes are heap-allocated and require careful memory management. The Big Three pattern also extends to the "Rule of Five" in C++11 with move semantics, which you may encounter in later coursework or industry code.
pointer, dereference, address-of, ampersand, &, , new, delete, dynamic memory, heap, stack, memory leak, dangling pointer, shallow copy, deep copy, copy constructor, destructor, assignment operator, Big Three, Rule of Three, const, const member function, int*, double pointer, pointer to pointer, CS 225, UIUC, data structures, C++ memory management