C++ Dynamic Memory Quiz

C++
0 Passed
0% acceptance

Comprehensive C++ quiz exploring dynamic memory management including new/delete operators, array allocation, memory leaks, dangling pointers, and the motivation for smart pointers in modern C++.

35 Questions
~70 minutes
1

Question 1

When allocating memory dynamically using the new operator for a single object, what happens if the allocation fails and how should this be handled in robust C++ code?

cpp
#include <iostream>
#include <new>  // For std::bad_alloc

int main() {
    try {
        int* ptr = new int(42);
        std::cout << "Allocation successful: " << *ptr << std::endl;
        delete ptr;
    } catch(const std::bad_alloc& e) {
        std::cout << "Allocation failed: " << e.what() << std::endl;
    }
    
    return 0;
}
A
new throws std::bad_alloc exception on failure, requiring try-catch blocks or nothrow version for robust error handling and preventing undefined behavior from null pointer dereference
B
new returns nullptr on failure like malloc
C
Allocation failures are automatically handled by the runtime
D
new never fails in modern systems
2

Question 2

What is the most critical difference between new/delete and new[]/delete[] operators when managing dynamically allocated arrays?

A
new[] calls constructors for each element and stores array size, while delete[] calls destructors for all elements and uses stored size for proper cleanup, making them non-interchangeable
B
new and new[] are functionally identical
C
delete[] is optional and can be replaced with delete
D
Array allocation doesn't require special operators
3

Question 3

When does a dangling pointer occur in dynamic memory management, and what are the immediate consequences of dereferencing such a pointer?

cpp
#include <iostream>

int main() {
    int* ptr = new int(42);
    std::cout << "Before delete: " << *ptr << std::endl;
    
    delete ptr;  // Memory freed, but ptr still points there
    // ptr is now a dangling pointer!
    
    // *ptr;  // UNDEFINED BEHAVIOR - could crash or show garbage
    
    ptr = nullptr;  // Good practice: set to null after delete
    
    return 0;
}
A
Dangling pointers occur when accessing memory after delete, causing undefined behavior that may crash programs, corrupt data, or create security vulnerabilities through use-after-free exploits
B
Dangling pointers are automatically detected and prevented
C
Dangling pointers only occur with automatic variables
D
Dereferencing dangling pointers is always safe
4

Question 4

What is a memory leak in the context of dynamic memory allocation, and why is it particularly problematic in long-running applications?

A
Memory leaks occur when allocated memory is never deallocated, causing gradual memory consumption that can lead to performance degradation and eventual program termination in long-running applications
B
Memory leaks are automatically detected and cleaned up by the operating system
C
Memory leaks only affect short-running programs
D
Memory leaks are impossible in modern C++
5

Question 5

What fundamental problem with manual memory management using new/delete led to the development of smart pointers in C++?

cpp
#include <iostream>

class Resource {
public:
    Resource() { std::cout << "Resource acquired" << std::endl; }
    ~Resource() { std::cout << "Resource released" << std::endl; }
};

void riskyFunction() {
    Resource* res = new Resource();
    // What if exception thrown here?
    // Resource would leak!
    
    doSomethingThatMightThrow();
    
    delete res;  // This might never execute
}

int main() {
    try {
        riskyFunction();
    } catch(...) {
        std::cout << "Exception caught, but resource leaked!" << std::endl;
    }
    return 0;
}
A
Manual memory management lacks exception safety and requires explicit cleanup in every code path, leading to leaks when exceptions occur or early returns happen, motivating RAII and smart pointers
B
Smart pointers were developed to make memory management slower
C
Manual memory management was already perfect
D
Smart pointers eliminate the need for any memory management
6

Question 6

When using new[] to allocate an array of objects with constructors, what additional memory overhead is typically required and why?

A
new[] stores the array size for proper destructor calls during delete[], requiring extra memory that enables correct cleanup when the exact number of elements is unknown to delete[]
B
new[] has no additional overhead compared to new
C
new[] only stores constructor call information
D
Array allocation requires no size tracking
7

Question 7

What is the most dangerous consequence of mixing new[] with delete instead of delete[] for array deallocation?

cpp
#include <iostream>

class TestClass {
public:
    TestClass() { std::cout << "Constructor" << std::endl; }
    ~TestClass() { std::cout << "Destructor" << std::endl; }
};

int main() {
    TestClass* arr = new TestClass[3];  // Calls 3 constructors
    
    // delete arr;  // WRONG: only calls 1 destructor, leaks 2 objects
    delete[] arr;  // CORRECT: calls all 3 destructors
    
    return 0;
}
A
Using delete instead of delete[] on arrays causes only the first element's destructor to be called, leaving other elements with uncalled destructors and causing resource leaks or undefined behavior
B
The operators are interchangeable for arrays
C
Mixing operators has no consequences
D
delete[] is never necessary
8

Question 8

In a function that allocates memory with new and returns a pointer to it, what ownership transfer responsibility must be clearly documented?

A
The function must document that the caller receives ownership and is responsible for calling delete, establishing clear ownership transfer semantics to prevent memory leaks
B
Ownership is automatically transferred without documentation
C
Functions cannot transfer ownership of allocated memory
D
Memory ownership is irrelevant in function returns
9

Question 9

What happens when an exception is thrown between new allocation and corresponding delete in a function, and how does this create memory leaks?

cpp
#include <iostream>
#include <stdexcept>

void leakyFunction() {
    int* ptr1 = new int(1);
    
    // Allocation successful
    std::cout << "First allocation: " << *ptr1 << std::endl;
    
    // Simulate some work that might fail
    throw std::runtime_error("Something went wrong!");
    
    // This code never executes!
    int* ptr2 = new int(2);  // Never reached
    delete ptr1;             // Never reached - LEAK!
    delete ptr2;             // Never reached
}

int main() {
    try {
        leakyFunction();
    } catch(const std::exception& e) {
        std::cout << "Exception: " << e.what() << std::endl;
        // Memory leaked!
    }
    return 0;
}
A
Exceptions prevent delete execution, causing allocated memory to leak since stack unwinding doesn't automatically deallocate heap memory, requiring RAII for exception safety
B
Exceptions automatically clean up dynamically allocated memory
C
Memory leaks don't occur with exceptions
D
Exceptions only affect stack variables
10

Question 10

Why do smart pointers provide better exception safety compared to raw pointers with manual memory management?

A
Smart pointers use RAII to automatically call delete in destructors, ensuring cleanup even when exceptions occur, eliminating manual delete requirements and exception safety concerns
B
Smart pointers make memory management slower
C
Smart pointers require more manual intervention than raw pointers
D
Smart pointers don't improve exception safety
11

Question 11

What is the primary reason that placement new requires manual destructor calls for proper cleanup?

cpp
#include <iostream>
#include <new>  // For placement new

class Test {
public:
    Test() { std::cout << "Constructor" << std::endl; }
    ~Test() { std::cout << "Destructor" << std::endl; }
};

int main() {
    char buffer[sizeof(Test)];
    
    Test* obj = new (buffer) Test();  // Placement new
    
    // Manual destructor call required!
    obj->~Test();
    
    // No delete - memory wasn't allocated by new
    
    return 0;
}
A
Placement new constructs objects in pre-allocated memory without allocation tracking, requiring explicit destructor calls since delete doesn't know construction occurred
B
Placement new automatically handles destruction
C
Placement new doesn't require any cleanup
D
Placement new uses the same cleanup as regular new
12

Question 12

When implementing a custom memory allocator that uses placement new, what destructor calling pattern ensures proper cleanup of constructed objects?

A
Explicit destructor calls using object->~Type() ensure proper cleanup in custom allocators, as the memory deallocation is separate from object destruction
B
Custom allocators automatically call destructors
C
Destructor calls are never needed in custom allocators
D
delete operator handles both destruction and deallocation in custom allocators
13

Question 13

What is the most significant performance overhead introduced by frequent dynamic memory allocations in performance-critical code?

A
Heap allocation involves system calls and memory searching, causing cache misses and fragmentation that can be orders of magnitude slower than stack allocation in tight loops
B
Dynamic allocation is always faster than stack allocation
C
Performance overhead is negligible in modern systems
D
Heap allocation has no performance impact
14

Question 14

In a multi-threaded program where multiple threads allocate and deallocate memory concurrently, what synchronization mechanism prevents heap corruption?

A
Heap allocators use internal locking mechanisms to prevent concurrent access corruption, though this can create contention and reduce performance in high-allocation scenarios
B
Multi-threaded memory allocation doesn't require synchronization
C
Heap corruption is impossible in multi-threaded programs
D
Each thread must implement its own synchronization
15

Question 15

When designing a class that manages dynamic memory, what destructor implementation ensures proper cleanup regardless of how the object is destroyed?

cpp
#include <iostream>

class MemoryManager {
private:
    int* data;
    size_t size;
public:
    MemoryManager(size_t s) : data(new int[s]), size(s) {
        std::cout << "Allocated " << size << " integers" << std::endl;
    }
    
    ~MemoryManager() {
        delete[] data;  // Always called, even with exceptions
        std::cout << "Deallocated memory" << std::endl;
    }
};

int main() {
    MemoryManager mgr(100);
    // Destructor automatically called when mgr goes out of scope
    return 0;
}
A
RAII destructor ensures cleanup occurs during stack unwinding, preventing leaks even when exceptions occur or objects are destroyed in complex scenarios
B
Destructors are optional for memory management
C
Manual cleanup is more reliable than destructors
D
Destructors only run in normal program termination
16

Question 16

What is the fundamental design principle that smart pointers implement to solve manual memory management problems?

A
RAII principle ensures automatic resource cleanup through destructor calls, providing exception safety and eliminating manual delete requirements in normal code paths
B
Smart pointers make memory management more complex
C
Smart pointers require more manual intervention than raw pointers
D
RAII is not related to smart pointer design
17

Question 17

When implementing a container class that holds pointers to dynamically allocated objects, what ownership model prevents memory leaks?

A
Clear ownership semantics with documented responsibility for deletion prevents leaks, requiring either container ownership with destructor cleanup or external ownership documentation
B
Container classes automatically manage pointer ownership
C
Ownership models are irrelevant for containers
D
Pointers in containers never cause memory leaks
18

Question 18

What is the most effective strategy for detecting memory leaks in large C++ applications during development?

A
Using memory debugging tools like Valgrind or Visual Studio's memory diagnostics with regular testing provides leak detection and allocation tracking throughout development
B
Memory leaks are impossible to detect during development
C
Code review is sufficient for leak detection
D
Memory leaks only appear in production environments
19

Question 19

When converting legacy code that uses raw pointers to smart pointers, what is the most challenging aspect of the migration process?

A
Determining ownership semantics and ensuring single ownership for unique_ptr or shared ownership for shared_ptr requires careful analysis of pointer usage patterns throughout the codebase
B
Smart pointer migration is always straightforward
C
Raw pointers cannot be converted to smart pointers
D
Ownership semantics are automatically determined
20

Question 20

What is the primary advantage of using std::unique_ptr over raw pointers for exclusive ownership scenarios?

cpp
#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource created" << std::endl; }
    ~Resource() { std::cout << "Resource destroyed" << std::endl; }
};

std::unique_ptr<Resource> createResource() {
    return std::unique_ptr<Resource>(new Resource());
}

int main() {
    auto res = createResource();
    // Automatic cleanup when res goes out of scope
    return 0;
}
A
std::unique_ptr provides automatic memory cleanup with move semantics, preventing leaks and ensuring single ownership without manual delete calls
B
std::unique_ptr makes memory management more complex
C
Raw pointers are superior to std::unique_ptr
D
std::unique_ptr requires manual memory management
21

Question 21

In a program that needs to allocate memory in a loop, what allocation strategy minimizes heap fragmentation and improves performance?

A
Pre-allocating memory in larger chunks and sub-allocating reduces allocation frequency and fragmentation, improving cache locality and reducing heap management overhead
B
Allocating small blocks in loops maximizes performance
C
Allocation strategy has no impact on fragmentation
D
Heap fragmentation is impossible to minimize
22

Question 22

What is the most critical consideration when overriding operator new and operator delete for a class?

A
Custom operators must maintain standard signatures and exception behavior, ensuring compatibility with standard library and preventing undefined behavior in generic code
B
Custom operators can have any signature
C
Overriding operators is never necessary
D
Standard signatures are irrelevant for custom operators
23

Question 23

When implementing a memory pool allocator, what bookkeeping information must be tracked for each allocation?

A
Allocation size and ownership information enables proper deallocation and leak detection, requiring metadata storage alongside allocated memory blocks
B
Memory pools don't require any bookkeeping
C
Only allocation size needs tracking
D
Bookkeeping information is automatically managed
24

Question 24

What is the fundamental problem that nothrow new attempts to solve in memory allocation?

cpp
#include <iostream>
#include <new>  // For nothrow

int main() {
    // Standard new - throws on failure
    try {
        int* ptr1 = new int;
        delete ptr1;
    } catch(const std::bad_alloc&) {
        std::cout << "Standard new failed" << std::endl;
    }
    
    // Nothrow new - returns nullptr on failure
    int* ptr2 = new (std::nothrow) int;
    if(!ptr2) {
        std::cout << "Nothrow new failed" << std::endl;
    } else {
        delete ptr2;
    }
    
    return 0;
}
A
Nothrow new provides exception-free allocation by returning nullptr instead of throwing, enabling error handling in code that cannot use exceptions
B
Nothrow new eliminates allocation failures
C
Nothrow new is faster than standard new
D
Nothrow new has no practical benefits
25

Question 25

In a real-time system where memory allocation timing must be predictable, what allocation strategy should be avoided?

A
Heap allocation should be avoided due to unpredictable timing from memory searching and potential system calls, favoring pre-allocated pools or stack allocation for real-time constraints
B
All allocation strategies have predictable timing
C
Heap allocation is always predictable
D
Real-time systems don't require predictable allocation
26

Question 26

What is the most significant advantage of using std::shared_ptr over manual reference counting for shared ownership?

A
std::shared_ptr automates reference counting with atomic operations, preventing leaks and use-after-free while handling cyclic references through weak_ptr when needed
B
Manual reference counting is superior to std::shared_ptr
C
std::shared_ptr makes memory management more complex
D
std::shared_ptr doesn't prevent cyclic references
27

Question 27

When implementing a custom deleter for smart pointers, what is the most important design consideration?

cpp
#include <iostream>
#include <memory>
#include <cstdio>  // For fopen/fclose

int main() {
    // Custom deleter for FILE*
    auto fileDeleter = [](FILE* f) {
        if(f) fclose(f);
    };
    
    std::unique_ptr<FILE, decltype(fileDeleter)> file(
        fopen("test.txt", "w"), fileDeleter);
    
    if(file) {
        fprintf(file.get(), "Hello, World!");
    }
    // File automatically closed by custom deleter
    
    return 0;
}
A
Custom deleters must properly clean up resources using appropriate APIs, ensuring type safety and preventing resource leaks when smart pointers go out of scope
B
Custom deleters are never necessary
C
All resources use the same cleanup mechanism
D
Custom deleters make smart pointers less useful
28

Question 28

What is the primary reason that memory-mapped I/O requires different memory management strategies than heap allocation?

A
Memory-mapped regions are managed by the operating system and require unmapping rather than deletion, using different APIs and lifetime management than heap-allocated memory
B
Memory-mapped I/O uses the same management as heap allocation
C
Memory mapping doesn't require any management
D
Heap allocation is more complex than memory mapping
29

Question 29

In a program that needs to allocate memory with specific alignment requirements, what is the most portable approach using standard C++?

A
Using std::aligned_alloc or alignment-aware allocators ensures proper alignment for SIMD operations or hardware requirements, preventing alignment-related performance penalties
B
Alignment requirements are automatically handled by new
C
Custom alignment is impossible in standard C++
D
Alignment has no impact on performance
30

Question 30

What is the most challenging aspect of debugging memory corruption caused by buffer overflows in dynamically allocated memory?

A
Buffer overflows can corrupt heap metadata or adjacent allocations, causing delayed crashes that are difficult to trace back to the original overflow location
B
Memory corruption is always immediately detectable
C
Buffer overflows don't affect dynamically allocated memory
D
Debugging memory corruption is straightforward
31

Question 31

When designing a memory allocator for a high-throughput server application, what is the most important performance consideration?

A
Minimizing lock contention through lock-free allocation or per-thread arenas maximizes throughput, as allocation is often a bottleneck in concurrent server applications
B
Performance is not a consideration in memory allocators
C
Lock contention has no impact on server performance
D
Single-threaded allocation is always fastest
32

Question 32

What is the fundamental principle behind garbage collection that smart pointers partially implement?

A
Automatic memory reclamation through reference tracking eliminates manual deallocation, though smart pointers use compile-time ownership while garbage collectors use runtime tracking
B
Smart pointers provide full garbage collection
C
Garbage collection is unrelated to smart pointers
D
Smart pointers are less effective than garbage collection
33

Question 33

In a program that needs to transfer ownership of dynamically allocated objects between functions, what smart pointer operation provides the most efficient transfer mechanism?

cpp
#include <iostream>
#include <memory>

std::unique_ptr<int> createObject() {
    return std::unique_ptr<int>(new int(42));
}

void processObject(std::unique_ptr<int> obj) {
    std::cout << "Processing: " << *obj << std::endl;
    // Ownership automatically transferred and cleaned up
}

int main() {
    auto obj = createObject();
    processObject(std::move(obj));  // Efficient ownership transfer
    // obj is now empty
    return 0;
}
A
Move semantics with std::move enable zero-cost ownership transfer, avoiding reference counting overhead while ensuring single ownership semantics
B
Ownership transfer requires copying objects
C
Smart pointers cannot transfer ownership
D
std::move makes ownership transfer less efficient
34

Question 34

What is the most significant limitation of std::auto_ptr compared to modern smart pointers?

A
std::auto_ptr uses copy semantics for ownership transfer, causing unexpected ownership changes during copying and making it unsuitable for standard containers
B
std::auto_ptr is superior to modern smart pointers
C
Modern smart pointers have the same limitations
D
std::auto_ptr has no limitations
35

Question 35

When implementing a resource manager that must clean up multiple types of resources, what smart pointer feature provides the most flexible cleanup mechanism?

A
Custom deleters enable type-specific cleanup logic, allowing smart pointers to manage any resource type with appropriate destruction semantics
B
Smart pointers only work with heap-allocated memory
C
Custom deleters make smart pointers less useful
D
All resources use identical cleanup mechanisms

QUIZZES IN C++