AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

07 Memory, ownership and RAII / Checkpoint

Checkpoint — Memory, ownership and RAII

25 min · +25 XP · 3 exercises · 14 questions

Exercises0/3
Quiz · 70% to clear14 q

This checkpoint covers the whole module: the four storage durations and the stack-frame picture, new/delete and the ownership contract, RAII and the guarantees behind it, std::unique_ptr, std::shared_ptr with std::weak_ptr, and the memory-error catalogue with the tools that find it.

How it works. Fourteen questions and three programs. You need 70% on the questions and every program accepted to clear the module. You can retake it as often as you like; your best score counts.

Before you start, make sure you can answer these from memory:

  • In what order are three automatic objects declared in one block destroyed, and does an early return change it?
  • What is undefined about delete on a block that came from new[]?
  • What runs between a throw and the catch that handles it?
  • What does std::move leave in a std::unique_ptr, and can you test it?
  • Why does a weak_ptr break a shared_ptr cycle, and what does lock() return when the object is gone?
  • Which three stacks does an AddressSanitizer use-after-free report show?

The three programs are a growable stack that owns its array with new[]/delete[], a ledger of std::shared_ptr handles whose use_count you print, and a task pipeline where a scope guard and a factory's unique_ptr must both behave on the exception path. Every destructor message is in a defined order — trace it on paper before you run.

Common questions

What does std::move leave in a std::unique_ptr?

A null pointer. After auto b = std::move(a); ownership has passed to b, and a is guaranteed to be empty — a defined, testable state, so if (a) is false, and dereferencing a would be undefined behaviour.

What runs between a throw and the catch that handles it?

Stack unwinding: every automatic object in every frame being abandoned is destroyed, in reverse order of construction, before the handler runs. Destructors therefore print their messages before the catch block does, which is what makes RAII cleanup work on the exception path.

What does lock() on a weak_ptr return when the object is gone?

An empty std::shared_ptr. A weak_ptr does not keep its object alive, so once the last owning shared_ptr is destroyed, lock() returns an empty pointer that tests false, and expired() returns true. Always test the result of lock() before using it.

Which three stacks does an AddressSanitizer use-after-free report show?

Where the bad access happened, where the memory was freed, and where it was originally allocated. Together they tell the whole story of a use-after-free: which owner released the block and which borrower kept using it afterwards.

C++ Memory Management Quiz: RAII and Smart Pointers Practice

Test your C++ memory management with 14 questions and three programs on the stack and heap, new and delete, RAII, smart pointers and memory-error tools.

  • Course: C++ study plan
  • Module: Memory, ownership and RAII
  • Kind: Checkpoint — cleared at 70%
  • Reading time: 25 min
  • Runtime: Clang 18 · C++20

Checkpoint — Memory, ownership and RAII is the checkpoint that closes the Memory, ownership and RAII module: a graded quiz and whole-program exercises, passed at 70%.

Instructions

This checkpoint covers the whole module: the four storage durations and the stack-frame picture, new/delete and the ownership contract, RAII and the guarantees behind it, std::unique_ptr, std::shared_ptr with std::weak_ptr, and the memory-error catalogue with the tools that find it.

How it works. Fourteen questions and three programs. You need 70% on the questions and every program accepted to clear the module. You can retake it as often as you like; your best score counts.

Before you start, make sure you can answer these from memory:

  • In what order are three automatic objects declared in one block destroyed, and does an early return change it?
  • What is undefined about delete on a block that came from new[]?
  • What runs between a throw and the catch that handles it?
  • What does std::move leave in a std::unique_ptr, and can you test it?
  • Why does a weak_ptr break a shared_ptr cycle, and what does lock() return when the object is gone?
  • Which three stacks does an AddressSanitizer use-after-free report show?

The three programs are a growable stack that owns its array with new[]/delete[], a ledger of std::shared_ptr handles whose use_count you print, and a task pipeline where a scope guard and a factory's unique_ptr must both behave on the exception path. Every destructor message is in a defined order — trace it on paper before you run.

Common questions

What does std::move leave in a std::unique_ptr?

A null pointer. After auto b = std::move(a); ownership has passed to b, and a is guaranteed to be empty — a defined, testable state, so if (a) is false, and dereferencing a would be undefined behaviour.

What runs between a throw and the catch that handles it?

Stack unwinding: every automatic object in every frame being abandoned is destroyed, in reverse order of construction, before the handler runs. Destructors therefore print their messages before the catch block does, which is what makes RAII cleanup work on the exception path.

What does lock() on a weak_ptr return when the object is gone?

An empty std::shared_ptr. A weak_ptr does not keep its object alive, so once the last owning shared_ptr is destroyed, lock() returns an empty pointer that tests false, and expired() returns true. Always test the result of lock() before using it.

Which three stacks does an AddressSanitizer use-after-free report show?

Where the bad access happened, where the memory was freed, and where it was originally allocated. Together they tell the whole story of a use-after-free: which owner released the block and which borrower kept using it afterwards.

Exercises

A growable stack over raw storage

Write IntStack, a stack that owns an int array obtained with new int[capacity], starting at capacity 2. When a push finds the array full, allocate one of double the capacity, copy the elements across, release the old block with delete[], and print grow <old> -> <new>. The destructor releases the block and prints released capacity <capacity>. Copying is deleted.

Commands, one per line: push <v>; pop (print popped <v> or empty); top (print top=<v> or empty); size (print size=<k>); print (print stack: <bottom> … <top> or stack: (empty)). Popping never shrinks the array.

Input: commands. Output: the command output, the grow lines as they happen, and the destructor's line last.

push 1
push 2
push 3
size
print
pop
pop
top
pop
pop

prints

grow 2 -> 4
size=3
stack: 1 2 3
popped 3
popped 2
top=1
popped 1
empty
released capacity 4

A ledger of shared handles

Named handles own resources through std::shared_ptr; several handles may share one resource. Resource has a name and a destructor that prints freed <name>. Keep the handles in a std::map<std::string, std::shared_ptr<Resource>> and process commands:

  • create <handle> <resource> — if the handle exists print handle <handle> in use; otherwise std::make_shared the resource and print <resource> owners=<use_count>;
  • alias <new> <existing> — if existing is unknown print unknown handle <existing>; if new is taken print handle <new> in use; otherwise copy the pointer and print <resource> owners=<use_count>;
  • release <handle> — if unknown print unknown handle <handle>; otherwise erase it: when other owners remain print <resource> owners=<remaining>, and when it was the last owner the destructor's freed line is the only output.

When the input ends, release every remaining handle in alphabetical order of handle name, with exactly the output an explicit release would give — so every resource is freed before main returns and the order is defined.

Input: commands. Output: one line per command, then the end-of-input releases.

create a disk
alias b a
create c net
release a
release b
release c

prints

disk owners=1
disk owners=2
net owners=1
disk owners=1
freed disk
freed net

A pipeline on the exception path

Combine a scope guard with a unique_ptr factory and watch both on the exception path. ScopeGuard prints start <id> in its constructor and cleanup <id> in its destructor. Write:

  • std::unique_ptr<Result> run_task(const std::string& id, long long value) — declare a ScopeGuard for id; if value is negative throw std::invalid_argument("negative value for " + id); otherwise return a Result made with std::make_unique holding id and value * value;
  • long long store(std::unique_ptr<Result> result) — a sink: print stored <id> -> <square> and return the square.

main reads n tasks. For each, inside a try, call run_task and pass the result to store with std::move, adding the square to a total; catch the std::invalid_argument and print error: <what()>. Finally print results=<stored> total=<sum>. The guard's cleanup line must come before both stored (the guard dies when run_task returns) and error (unwinding runs it before the handler).

Input: n, then n lines of <id> <value> (|value| ≤ 1 000 000). Output: three lines per task, then the summary.

3
a 3
b -2
c 10

prints

start a
cleanup a
stored a -> 9
start b
cleanup b
error: negative value for b
start c
cleanup c
stored c -> 100
results=2 total=109

In this module: Memory, ownership and RAII

← Memory errors and the tools that find them · Structs and classes — a type of your own →