Skip to content

Module 01: Memory Allocation, References, and Pointers

Download Official Subject PDF

Key Concepts:

  • Stack vs Heap allocation
  • new and delete operators
  • References vs Pointers
  • Pointers to members
  • switch statements

void function() {
int x = 42; // On stack
Zombie zombie; // On stack
} // Automatically destroyed here

Characteristics:

  • Fast allocation/deallocation
  • Limited size (~1-8 MB typically)
  • Automatic cleanup when scope ends
  • Cannot outlive function
void function() {
int* x = new int(42); // On heap
Zombie* zombie = new Zombie(); // On heap
// ... use them ...
delete x; // Must manually delete
delete zombie; // Must manually delete
}

Characteristics:

  • Slower allocation
  • Large capacity (limited by RAM)
  • Must be manually freed
  • Can outlive function (return pointer)

// Allocation
int* ptr = new int; // Uninitialized
int* ptr = new int(); // Zero-initialized
int* ptr = new int(42); // Initialized to 42
// Deallocation
delete ptr;
ptr = NULL; // Good practice (C++98)
// Allocation
int* arr = new int[10]; // Array of 10 ints
Zombie* horde = new Zombie[5]; // Array of 5 Zombies
// Deallocation - NOTE THE []
delete[] arr; // MUST use delete[] for arrays
delete[] horde;
// WRONG: Using delete on array
int* arr = new int[10];
delete arr; // UNDEFINED BEHAVIOR!
// WRONG: Using delete[] on single object
int* ptr = new int(42);
delete[] ptr; // UNDEFINED BEHAVIOR!
// WRONG: Double delete
int* ptr = new int(42);
delete ptr;
delete ptr; // CRASH or corruption!
Use Stack When…Use Heap When…
Object’s lifetime = function scopeObject must outlive function
Size known at compile timeSize determined at runtime
Small objectsLarge objects
Performance criticalReturning new objects

A reference is an alias - another name for an existing variable.

int x = 42;
int& ref = x; // ref IS x (not a copy, not a pointer)
ref = 100; // Changes x to 100
std::cout << x; // Prints 100
FeatureReferencePointer
Syntaxint& ref = x;int* ptr = &x;
Can be nullNoYes
Can be reassignedNoYes
Must be initializedYesNo
Access valueref*ptr
Access address&refptr
// MUST initialize
int& ref; // ERROR: references must be initialized
int& ref = x; // OK
// CANNOT be null
int& ref = NULL; // ERROR: cannot bind to null
// CANNOT be reassigned
int& ref = x;
ref = y; // This doesn't reassign ref, it copies y to x!
// BAD: Passing by value (copies entire object)
void printZombie(Zombie z) {
// z is a COPY - expensive for large objects
}
// BETTER: Passing by pointer
void printZombie(Zombie* z) {
if (z != NULL) // Must check for null
std::cout << z->getName();
}
// BEST: Passing by reference
void printZombie(Zombie& z) {
// z is the original object - no copy
// Cannot be null - no check needed
std::cout << z.getName();
}
// CONST reference - read-only access
void printZombie(const Zombie& z) {
std::cout << z.getName(); // Can read
// z.setName("X"); // ERROR: z is const
}
SituationUse
Never null, won’t change what it refers toReference
Might be nullPointer
Needs to be reassignedPointer
Return new object from functionPointer (or smart pointer in modern C++)
Optional parameterPointer (can pass NULL)

std::string str = "HI THIS IS BRAIN";
std::string* stringPTR = &str; // Pointer TO str
std::string& stringREF = str; // Reference (alias) OF str
// Addresses - all should be the same!
std::cout << &str << std::endl; // Address of str
std::cout << stringPTR << std::endl; // Pointer holds same address
std::cout << &stringREF << std::endl; // Address of ref = address of str
// Values - all should be the same!
std::cout << str << std::endl; // Direct access
std::cout << *stringPTR << std::endl; // Dereference pointer
std::cout << stringREF << std::endl; // Reference is same as original

class HumanA {
private:
std::string _name;
Weapon& _weapon; // Reference - MUST always have a weapon
public:
// MUST initialize reference in constructor
HumanA(std::string name, Weapon& weapon)
: _name(name), _weapon(weapon) {}
void attack() {
std::cout << _name << " attacks with " << _weapon.getType();
}
};
// Usage
Weapon club("club");
HumanA bob("Bob", club); // MUST provide weapon at construction
class HumanB {
private:
std::string _name;
Weapon* _weapon; // Pointer - might not have a weapon
public:
HumanB(std::string name) : _name(name), _weapon(NULL) {}
void setWeapon(Weapon& weapon) {
_weapon = &weapon;
}
void attack() {
if (_weapon)
std::cout << _name << " attacks with " << _weapon->getType();
else
std::cout << _name << " has no weapon";
}
};
// Usage
HumanB jim("Jim"); // No weapon initially
jim.setWeapon(club); // Weapon added later
  • Reference: When object MUST exist at construction and throughout lifetime
  • Pointer: When object might not exist, or might change

void sayHello() { std::cout << "Hello"; }
void sayBye() { std::cout << "Bye"; }
// Function pointer
void (*funcPtr)() = &sayHello;
funcPtr(); // Calls sayHello()
funcPtr = &sayBye;
funcPtr(); // Calls sayBye()
class Harl {
public:
void debug() { std::cout << "Debug"; }
void info() { std::cout << "Info"; }
void warning() { std::cout << "Warning"; }
void error() { std::cout << "Error"; }
void complain(std::string level) {
// Array of member function pointers
void (Harl::*funcs[4])() = {
&Harl::debug,
&Harl::info,
&Harl::warning,
&Harl::error
};
std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
for (int i = 0; i < 4; i++) {
if (level == levels[i]) {
(this->*funcs[i])(); // Call the member function
return;
}
}
}
};
// Declaration
void (ClassName::*pointerName)(parameters);
// Assignment
pointerName = &ClassName::methodName;
// Call via object
(object.*pointerName)(args);
// Call via pointer to object
(objectPtr->*pointerName)(args);

switch (expression) {
case value1:
// code
break;
case value2:
// code
break;
default:
// code if no match
}
// WITHOUT break - falls through to next case
switch (level) {
case 3: // WARNING
std::cout << "Warning message\n";
// No break - falls through!
case 2: // INFO
std::cout << "Info message\n";
// No break - falls through!
case 1: // DEBUG
std::cout << "Debug message\n";
break;
}
// If level = 3: prints Warning, Info, Debug
// If level = 2: prints Info, Debug
// If level = 1: prints Debug
// Can only switch on integral types in C++98
switch (number) { ... } // OK
switch (character) { ... } // OK
switch (string) { ... } // ERROR in C++98!
// For strings, must use if-else
if (str == "DEBUG") { ... }
else if (str == "INFO") { ... }

Since C++98 cannot switch on strings, convert strings to integers first:

// Convert string to index for switch
int getLevel(const std::string& level) {
std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
for (int i = 0; i < 4; i++) {
if (level == levels[i])
return i;
}
return -1; // Not found
}
void complain(const std::string& level) {
switch (getLevel(level)) {
case 0:
std::cout << "Debug message" << std::endl;
break;
case 1:
std::cout << "Info message" << std::endl;
break;
case 2:
std::cout << "Warning message" << std::endl;
break;
case 3:
std::cout << "Error message" << std::endl;
break;
default:
std::cout << "Unknown level" << std::endl;
}
}

This is cleaner than a long if-else chain and allows fall-through behavior.


#include <fstream>
#include <string>
std::ifstream inFile("input.txt");
if (!inFile.is_open()) {
std::cerr << "Cannot open file" << std::endl;
return 1;
}
std::string line;
while (std::getline(inFile, line)) {
std::cout << line << std::endl;
}
inFile.close();
std::ofstream outFile("output.txt");
if (!outFile.is_open()) {
std::cerr << "Cannot create file" << std::endl;
return 1;
}
outFile << "Hello, World!" << std::endl;
outFile << "Line 2" << std::endl;
outFile.close();
std::ifstream inFile("input.txt");
std::string content;
std::string line;
while (std::getline(inFile, line)) {
content += line;
content += "\n";
}

// Single object
Type* ptr = new Type(args);
delete ptr;
// Array
Type* arr = new Type[size];
delete[] arr;
Type& ref = original; // Create alias
// ref is now indistinguishable from original
void (Class::*ptr)(args) = &Class::method;
(object.*ptr)(args);
std::ifstream in("file");
std::ofstream out("file");
std::getline(in, str);
out << content;