Memory & Language Features
Pointers
Complete lesson for Pointers using LEARNCODEGURU’s 6-level learning model — from basics to advanced practice, with C++ OOP and STL where relevant.
Basics — Pointers
Section: Memory & Language Features. C++ pointers — addresses, dynamic allocation, new/delete, and smart pointers (unique_ptr, shared_ptr, weak_ptr).
What is a pointer?
- A **pointer** is a variable that stores a **memory address** of another variable (or heap block).
- Declare:
int* p;— “pointer to int”. Initialize with address-of:p = &x; - **Dereference**
*preads/writes the object at that address. - **Real-life analogy:** a pointer is like a house address on a slip of paper;
*pis visiting the house. - **Real-life analogy:** a remote control (pointer) selects a TV (object); the remote is not the TV.
| Concept | Syntax | Meaning |
|---|---|---|
| Declare pointer | int* p; | p will hold an address of an int |
| Address-of | p = &x; | Store address of x in p |
| Dereference | *p = 20; | Change the int at that address |
| Null pointer | p = nullptr; | Points to nothing (safe empty) |
| Print address | cout << p; | Shows the stored address |
#include <iostream>
using namespace std;
int main() {
int x = 10;
int* p = &x;
cout << "x = " << x << "\n";
cout << "&x = " << &x << "\n";
cout << "p = " << p << "\n";
cout << "*p = " << *p << "\n";
*p = 25;
cout << "x now = " << x << "\n";
return 0;
} nullptr and pointer safety
- Use **
nullptr** (C++11) for an empty pointer — prefer it overNULLor0. - Never dereference a null or uninitialized pointer — undefined behaviour / crash.
- After
delete, set the pointer tonullptrso you do not keep a **dangling** address. - Uninitialized pointers hold garbage addresses — always initialize (
nullptror&varornew).
int* p = nullptr;
if (p != nullptr) {
cout << *p; // only if valid
} Pointers and arrays
- An array name can decay to a pointer to its first element:
int* p = a; p[i]is like*(p + i).- Pointer arithmetic moves in units of the pointed type (
p+1→ next int, not next byte).
int a[5] = {10, 20, 30, 40, 50};
int* p = a;
cout << *p << "\n"; // 10
cout << *(p + 2) << "\n"; // 30
cout << p[3] << "\n"; // 40 Static vs dynamic allocation (differences)
- **Static / automatic allocation:** size known mainly at compile time; memory managed for you (globals/statics duration or stack locals).
- **Dynamic allocation:** size decided at **runtime**; you allocate on the **heap** with
new/new[]and free withdelete/delete[]. - Pointers are how you hold and use dynamically allocated memory.
| Point | Static / automatic | Dynamic (heap) |
|---|---|---|
| When size is known | Mostly at compile time | At runtime (user input, varying needs) |
| Where memory lives | Data segment / **stack** (locals) | **Heap** |
| How you get it | int x;, int a[10]; | new int, new int[n] |
| Who frees it | Automatic (end of scope / program) | You (delete / delete[]) or smart pointers |
| Lifetime | Fixed by scope/storage duration | Until you free it (or process ends) |
| Flexibility | Limited for unknown sizes | High — grow/allocate as needed |
| Risk | Stack overflow if huge locals | Leaks, dangling pointers if mismanaged |
| Typical use | Small locals, fixed arrays | Large/variable arrays, linked structures |
| Access | Variable name directly | Through a pointer |
| Example | int marks[50]; | int* marks = new int[n]; |
Dynamic memory allocation in C++
- C++ uses **
new** / **new[]** to allocate and **delete** / **delete[]** to free (prefer these over C’smalloc/freein C++ code). - Allocated memory stays until freed — forgetting
deletecauses a **memory leak**. - Match pairs:
new↔delete,new[]↔delete[]. - **Real-life:** renting a hall for a wedding (allocate), use it, then return the keys (delete) — if you never return it, you keep paying (leak).
| Operator | Purpose | Example |
|---|---|---|
new | Allocate one object | int* p = new int; |
new T(value) | Allocate and construct/init | int* p = new int(10); |
new[] | Allocate an array | int* a = new int[5]; |
delete | Free one object | delete p; |
delete[] | Free an array | delete[] a; |
Dynamic memory — new, delete, heap & smart pointers
Dynamic memory is allocated at runtime from the heap using
new / new[] and freed with delete / delete[].
The diagram covers why it is used, stack vs heap, array/2D examples, leaks, dangling pointers,
and smart pointers.
- new / new[]: allocate one object or an array on the heap.
- delete / delete[]: free matching allocations — never mismatch.
- Leak: forgot delete · Dangling: use after delete.
- Prefer:
unique_ptr/vectorfor safer ownership.
Dynamic allocation — single object
int* p = new int;allocates one int on the heap;*puses it;delete p;frees it.- Initialize:
int* p = new int(42); - After delete:
p = nullptr;
#include <iostream>
using namespace std;
int main() {
int* p = new int(10);
cout << *p << "\n";
*p = 25;
cout << *p << "\n";
delete p;
p = nullptr;
return 0;
} Dynamic allocation — arrays
- Size can come from the user:
int n; cin >> n; int* a = new int[n]; - Index with
a[i]just like a normal array. - Free with **
delete[] a;** (notdelete a). - **Real-life:** create a marks list for N students only after N is entered.
#include <iostream>
using namespace std;
int main() {
int n;
cout << "How many marks? ";
cin >> n;
int* marks = new int[n];
for (int i = 0; i < n; i++) {
cin >> marks[i];
}
int sum = 0;
for (int i = 0; i < n; i++) sum += marks[i];
cout << "Average = " << (sum / (double)n) << "\n";
delete[] marks;
marks = nullptr;
return 0;
} Dynamic 2D arrays (array of pointers)
- One common pattern: allocate an array of row pointers, then each row with
new int[cols]. - Free in reverse:
delete[]each row, thendelete[]the row-pointer array. - **Real-life:** cinema seats rows×cols when dimensions are chosen at runtime.
#include <iostream>
using namespace std;
int main() {
int rows = 2, cols = 3;
int** mat = new int*[rows];
for (int r = 0; r < rows; r++)
mat[r] = new int[cols];
int k = 1;
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++)
mat[r][c] = k++;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) cout << mat[r][c] << " ";
cout << "\n";
}
for (int r = 0; r < rows; r++) delete[] mat[r];
delete[] mat;
return 0;
} Stack vs heap (where pointers fit)
- Local variables and the pointer variable itself often live on the **stack**.
newmemory lives on the **heap**; the pointer on the stack stores that heap address.- When a function returns, stack locals vanish — but heap blocks remain until
delete(or process exit).
| Region | Holds | Freed how |
|---|---|---|
| Stack | Locals, parameters, pointer variables | Automatic at end of scope |
| Heap | Objects from new / new[] | delete / delete[] / smart pointer |
Pointers vs references (quick)
- Pointers can be reseated and can be
nullptr; references must bind and usually cannot be null. - Prefer references for APIs when “must refer to an object”; use pointers for optional/ reseatable / dynamic ownership.
| Point | Pointer | Reference |
|---|---|---|
| Stores | Address (can change) | Alias (bound at init) |
| Null | Yes (nullptr) | No (normally) |
| Syntax | *p, p-> | r directly |
| Reseat | Yes p = &y; | No |
| Dynamic alloc | Yes — hold new result | Not for owning new memory |
Advanced — Pointers
Memory leaks and dangling pointers
- **Leak:** allocate with
newand neverdelete— memory stays reserved until the program ends. - **Dangling:** pointer still holds an address after
delete— using it is undefined behaviour. - **Double delete:** deleting the same block twice is also undefined behaviour.
- Fix dangling:
delete p; p = nullptr;and never use after free.
int* p = new int(5);
delete p; // OK
// *p = 1; // BAD — dangling
p = nullptr; // safer
// delete p; // OK on nullptr (no-op) new vs malloc (C++)
- In C++ prefer
new/delete: they call constructors/destructors for class types. malloc/freeare C-style; do not mixnewwithfreeormallocwithdelete.newcan throwstd::bad_allocon failure (unlessnothrowform).
| Feature | new / delete | malloc / free |
|---|---|---|
| Language | C++ | C (also usable in C++) |
| Constructors | Yes | No |
| Type safety | Typed result | void* (cast needed in C++) |
| Failure | Throws / nullptr (nothrow) | Returns null |
| Pairing | Match new/delete forms | Match malloc/free |
Smart pointers (modern C++)
- **Smart pointers** (
#include) own heap memory and free it automatically — RAII. - Prefer them over raw
new/deletein application code to avoid leaks and dangling pointers. - Main types:
unique_ptr,shared_ptr,weak_ptr. Prefermake_unique/make_sharedto create them. - **Real-life:** a smart locker that returns itself when you leave — you do not manually “delete” the room.
| Smart pointer | Ownership | Copy? | When to use |
|---|---|---|---|
unique_ptr | Exclusive (one owner) | No (movable only) | Default choice for single ownership |
shared_ptr | Shared (ref-counted) | Yes | Several owners need the same object |
weak_ptr | Non-owning observer | Yes (weak) | Break cycles; cache/observe without keeping alive |
unique_ptr — exclusive ownership
- Only one
unique_ptrowns the object at a time. - When the
unique_ptris destroyed (end of scope), it callsdeleteautomatically. - Move with
std::moveto transfer ownership; cannot copy. - Array form:
unique_ptrormake_unique.(n)
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> p = make_unique<int>(42);
cout << *p << "\n";
unique_ptr<int> q = std::move(p); // ownership moves to q
// p is now empty (nullptr)
if (!p) cout << "p is empty\n";
cout << *q << "\n";
// automatic delete when q goes out of scope
return 0;
} shared_ptr and weak_ptr
shared_ptrkeeps a **reference count** — object freed when the last owner is gone.- Copying a
shared_ptrincreases the count; destroying one decreases it. weak_ptrobserves ashared_ptr**without** increasing the count — uselock()to get a temporaryshared_ptr.- Use
weak_ptrto avoid **cycles** (A owns B and B owns A via shared_ptr → never freed).
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<string> a = make_shared<string>("Hello");
shared_ptr<string> b = a; // share ownership
cout << *a << " use_count=" << a.use_count() << "\n"; // 2
weak_ptr<string> w = a;
if (auto s = w.lock()) {
cout << "Still alive: " << *s << "\n";
}
return 0;
} Raw pointer vs smart pointer
- Use raw pointers for non-owning observation; use smart pointers when you own heap memory.
| Point | Raw T* | Smart pointer |
|---|---|---|
| Owns memory? | Only if you say so (manual) | Yes (by design) |
| Auto free? | No — you delete | Yes at end of ownership |
| Null? | nullptr OK | Can be empty |
| Best for | Optional / borrow / arrays APIs | Ownership of new objects |
| Leak risk | High if you forget delete | Low when used correctly |
Pointer to pointer and functions
int**is a pointer to a pointer — used for 2D dynamic arrays and out-parameters that reseat a pointer.- Passing
int*lets a function modify the pointed-to object; passingint*&orint**can reseat the caller’s pointer.
void setToHeap(int*& p, int value) {
delete p;
p = new int(value);
} Real-life models
- Hotel room booking: allocate a room (new), use it, checkout (delete) — or use unique_ptr so checkout is automatic.
- Playlist of unknown length: dynamic array/vector of song IDs.
- Linked list / tree nodes: each node allocated dynamically; prefer unique_ptr/shared_ptr for ownership.
- Optional sensor: raw/observer pointer is nullptr when device absent; non-null when connected.
- Document shared by many tabs: shared_ptr-like shared ownership until the last tab closes.
Tricky Interview Points
Q1) What is a pointer in C++?
Answer A variable that stores the memory address of another object. You create it with types like int*, set it with &x, and access the object with *p.
Q2) What is the difference between static/automatic and dynamic allocation?
Answer Static/automatic allocation has size known mainly at compile time and is managed automatically (stack/globals). Dynamic allocation uses the heap via new/new[], size can be runtime, and you must free with delete/delete[] (or smart pointers).
Q3) Where does new allocate memory?
Answer On the heap. The pointer variable that holds the address often lives on the stack.
Q4) Difference between new and new[]? delete and delete[]?
Answer new allocates one object; new[] allocates an array. Free with delete and delete[] respectively — never mismatch them.
Q5) What is a memory leak?
Answer Allocating heap memory and never freeing it, so it remains reserved until the program ends.
Q6) What is a dangling pointer?
Answer A pointer that still holds the address of memory that was already freed (or of a destroyed object). Using it is undefined behaviour.
Q7) Why use nullptr?
Answer It is the type-safe null pointer literal in C++11+. Prefer it over NULL or 0 for empty pointers.
Q8) What is pointer arithmetic?
Answer Adding/subtracting an integer moves the pointer by that many elements of the pointed type, e.g. p+1 points to the next int.
Q9) Can you allocate an array whose size is read from the user?
Answer Yes — dynamically: int n; cin>>n; int* a = new int[n]; … delete[] a;
Q10) new vs malloc in C++?
Answer Prefer new/delete because they are typed and invoke constructors/destructors. Do not mix new with free or malloc with delete.
Q11) What are smart pointers?
Answer RAII wrappers from <memory> (unique_ptr, shared_ptr, weak_ptr) that free owned heap memory automatically when ownership ends.
Q12) unique_ptr vs shared_ptr?
Answer unique_ptr has exclusive ownership and cannot be copied (only moved). shared_ptr shares ownership with reference counting and can be copied.
Q13) What is weak_ptr for?
Answer A non-owning observer of an object managed by shared_ptr. It does not keep the object alive and helps break ownership cycles. Use lock() to access safely.
Q14) Why prefer make_unique / make_shared?
Answer They are exception-safer and clearer than writing new yourself, and make_shared can be more efficient by combining control-block and object allocation.
Q15) Pointer vs reference?
Answer Pointers store addresses, can be null/reseated, use * and ->. References are aliases, must bind, usually non-null, and use direct syntax.
Simple Programs
Pointer basics — modify via *p
#include <iostream>
using namespace std;
int main() {
int x = 5;
int* p = &x;
*p = *p + 10;
cout << x << "\n"; // 15
return 0;
} Dynamic single int
#include <iostream>
using namespace std;
int main() {
int* p = new int(100);
cout << *p << "\n";
delete p;
p = nullptr;
return 0;
} Dynamic array — sum N numbers
#include <iostream>
using namespace std;
int main() {
int n = 5;
int* a = new int[n]{1, 2, 3, 4, 5};
int sum = 0;
for (int i = 0; i < n; i++) sum += a[i];
cout << sum << "\n";
delete[] a;
return 0;
} Static array vs dynamic array
#include <iostream>
using namespace std;
int main() {
int fixed[3] = {10, 20, 30}; // automatic/static-size
int n = 3;
int* dyn = new int[n]{10, 20, 30}; // dynamic
cout << fixed[1] << " " << dyn[1] << "\n";
delete[] dyn;
return 0;
} Swap using pointers
#include <iostream>
using namespace std;
void swap(int* a, int* b) {
int t = *a; *a = *b; *b = t;
}
int main() {
int x = 3, y = 7;
swap(&x, &y);
cout << x << " " << y << "\n";
return 0;
} unique_ptr demo
#include <iostream>
#include <memory>
using namespace std;
int main() {
auto p = make_unique<int[]>(5);
for (int i = 0; i < 5; i++) p[i] = (i + 1) * 10;
cout << p[2] << "\n"; // 30
return 0;
} shared_ptr — shared ownership
#include <iostream>
#include <memory>
#include <string>
using namespace std;
int main() {
auto a = make_shared<string>("C++");
auto b = a;
cout << *b << " owners=" << a.use_count() << "\n";
return 0;
} Leak vs correct free (demo comments)
#include <iostream>
using namespace std;
int main() {
int* leak = new int(1);
// forgot delete — leak (don't do this)
int* ok = new int(2);
cout << *ok << "\n";
delete ok;
ok = nullptr;
delete leak; // free for demo completeness
return 0;
} Problem Solving Tricks
- Always initialize pointers (
nullptr,&x, ornew). - Never dereference null or dangling pointers.
- Match new with delete and new[] with delete[].
- After delete, set the pointer to nullptr.
- Need runtime size? → dynamic allocation (or std::vector).
- Prefer std::vector / smart pointers over raw new[] in application code.
- Default owning pointer: unique_ptr; share only when needed: shared_ptr.
- Use weak_ptr to observe shared_ptr without keeping the object alive.
- Prefer make_unique / make_shared over naked new.
- Pointer arithmetic steps by element size, not bytes.
- Stack = automatic locals; heap = new memory.
- Do not mix malloc/free with new/delete.
- Draw boxes for stack pointer and heap block when debugging.
- const int* vs int* const — pointer to const vs const pointer.
- For ownership, think RAII: unique_ptr first.
Practice Programs List
- Print address and value of an int using a pointer.
- Change a variable’s value only through a pointer.
- Write a table (on paper) comparing static vs dynamic allocation with 5 points.
- Allocate one double with new, read/write it, delete it.
- Read n from user, allocate int[n], input values, print max, delete[].
- Create a dynamic 2D matrix rows×cols, fill, print, free correctly.
- Demonstrate a dangling pointer bug, then fix it with nullptr.
- Implement swap with pointers and with references; compare.
- Rewrite a dynamic array program using unique_ptr or vector.
- Show pointer arithmetic on an array of 5 ints.
- Write a function that takes int* and doubles each of n elements.
- Explain in comments stack vs heap for: int x; int* p = new int;
- Intentionally mismatch new[] with delete (in a throwaway build) and note why it is wrong — then fix it.
- List three real-life cases where dynamic allocation is needed.