Programming Fundamentals (Python/C++)

Chapter 7
Memory, References, Pointers, and Resource Management

Bachelors Program in Full Stack Development
Programming Fundamentals (Python/C++)   •   Bachelor

Memory, References, Pointers, and Resource Management

Introduction and Context

Programs manipulate data, but data must exist somewhere while a program runs. Understanding where objects are stored, how long they exist, and how different variables relate to the same object is essential for writing reliable software. Errors in this area can produce memory leaks, crashes, corrupted data, security vulnerabilities, and difficult-to-reproduce bugs.

Python and C++ express memory management differently. Python largely automates object lifetime through garbage collection and reference counting, while C++ gives programmers direct control over storage and ownership. Nevertheless, both languages require the same conceptual questions:

  • Where is an object stored?
  • Who owns it?
  • How long will it remain valid?
  • Does an assignment copy the object or refer to it?
  • Can another part of the program mutate it?
  • What happens when the object is no longer needed?

This chapter develops a unified model and then distinguishes the precise behavior of Python and C++.

Learning Objectives

By the end of this chapter, students should be able to:

1. Explain stack and heap storage and distinguish storage duration from physical memory location.

2. Describe object identity, value semantics, reference semantics, and mutability.

3. Use Python names and references correctly, avoiding unintended aliasing.

4. Explain C++ pointers, references, dynamic allocation, and pointer validity.

5. Compare copying, shallow copying, and deep copying.

6. Explain garbage collection, ownership, smart pointers, and RAII.

7. Identify and prevent leaks, dangling references, double deletion, and unsafe aliasing.

8. Select suitable resource-management techniques in practical software designs.

1. A Unified Model of Objects and Names

A useful starting point is to distinguish a name from an object. An object is a region of storage containing a value and, in many languages, metadata describing its type. A name is a way for a program to access an object.

Consider Python:

```python

x = [10, 20]

y = x

```

There is one list object and two names referring to it. The statement `y = x` does not normally create a second list. Thus:

```python

y.append(30)

print(x) # [10, 20, 30]

print(x is y) # True

```

The operator `is` tests object identity: whether two names designate the same object. The operator `==` tests value equality: whether two objects contain equivalent values. These are different concepts.

In C++, the same situation depends on the declaration:

```cpp

std::vector<int> x{10, 20};

std::vector<int> y = x; // value copy

std::vector<int>& r = x; // reference to x

std::vector<int>* p = &x; // pointer containing x's address

```

Here `y` is an independent vector, while `r` and `p` provide access to `x`. A C++ reference is an alias established during initialization; a pointer is an object that stores an address and can be changed to point elsewhere.

1.1 Identity, equality, and lifetime

Three properties should be considered separately:

  • Identity: whether two expressions designate the same object.
  • State or value: the data currently held by an object.
  • Lifetime: the period during which the object exists and can safely be used.

Two distinct objects may have equal values but different identities. An object may retain its identity while its state changes. A reference or pointer may remain stored in a variable after its target has been destroyed; it then becomes invalid, even though the variable itself still exists.

2. Stack and Heap Storage

The terms stack and heap describe common storage models, but they should not be treated as exact universal laws. Language standards define behavior and lifetime more strictly than they define physical memory layout.

2.1 Stack and automatic storage

The call stack supports function invocation. A function call commonly creates a stack frame containing parameters, return information, and local variables. In C++, a local variable such as:

```cpp

void process() {

int count = 10;

std::string name = "Ada";

}

```

has automatic storage duration. Its lifetime begins when execution reaches its declaration and ends when control leaves its scope. The `std::string` object is automatically destroyed at the end of the function, and its destructor releases any resources it owns.

Python implementation details differ. Python names and objects are managed by the interpreter, and an implementation may use a private heap for Python objects. The conceptual lesson remains: local bindings may cease to be accessible after a function returns, while referenced objects can remain alive if another reference exists.

Stack storage is efficient and naturally follows nested scopes, but it is limited in size. It is not suitable for objects that must outlive the function that created them unless the object is transferred or stored elsewhere.

2.2 Heap and dynamic storage

The heap is a region from which storage can be obtained dynamically. In C++, dynamic allocation traditionally uses `new` and `delete`:

```cpp

int* p = new int(42);

// use *p

delete p;

```

The pointer `p` is a local object, but the integer allocated by `new` has dynamic storage duration. It remains alive until explicitly released with the matching operation. Failure to release it creates a memory leak.

Modern C++ generally avoids direct ownership through raw `new` and `delete`. Smart pointers and standard containers express ownership more safely. Python normally allocates objects dynamically and reclaims them automatically when they are no longer reachable, subject to implementation behavior and external resources.

2.3 Storage duration versus location

C++ formally distinguishes storage durations such as automatic, dynamic, static, and thread storage duration. A compiler may optimize variables into registers, eliminate them, or place them in unexpected physical locations. Therefore, “stack object” and “heap object” are useful practical descriptions, not complete definitions of lifetime or performance.

A dynamically allocated object is not automatically better than a local object. Dynamic allocation introduces ownership and failure concerns. Prefer local variables, standard containers, and value types when their lifetimes are sufficient.

3. Value and Reference Semantics

Value semantics mean that an object behaves as an independent value. Copying it produces a logically independent object, so changing one does not change the other.

```cpp

int a = 5;

int b = a;

b = 9;

// a is still 5

```

C++ standard-library containers generally support value semantics:

```cpp

std::vector<int> first{1, 2, 3};

std::vector<int> second = first;

second[0] = 99;

// first[0] is still 1

```

Reference semantics mean that copying a handle, name, or reference preserves access to the same underlying object. Python assignment generally binds another name to an existing object:

```python

items = [1, 2, 3]

alias = items

alias[0] = 99

items is now [99, 2, 3]

```

This is not a defect; it is a consequence of sharing a mutable object. It becomes a defect when the sharing is unintended or undocumented.

3.1 Function arguments

Python passes object references by assignment, sometimes informally described as “call by object sharing.” A function can mutate a mutable argument, but rebinding its parameter does not rebind the caller’s name:

```python

def modify(values):

values.append(4) # changes the shared list

def rebind(values):

values = [100] # changes only the local parameter

numbers = [1, 2, 3]

modify(numbers)

rebind(numbers)

numbers is [1, 2, 3, 4]

```

C++ can pass by value, by reference, or by pointer:

```cpp

void by_value(std::vector<int> v); // copy is made

void by_reference(std::vector<int>& v); // caller's object may change

void read_only(const std::vector<int>& v); // no copy; cannot modify through v

```

A `const` reference is often efficient for read-only access to a large object. Passing by value can be preferable when the function needs an independent copy or when move semantics make transfer inexpensive.

4. Mutability and Aliasing

An object is mutable if its state can change after creation. Lists, dictionaries, and sets are mutable in Python. Integers, strings, tuples, and frozensets are immutable. In C++, mutability is controlled by the type and qualifiers such as `const`.

```python

text = "cat"

text[0] = "C" # TypeError: strings are immutable

text = "Cat" # creates or binds to another string value

```

Immutability reduces aliasing risk. If several parts of a program share an immutable object, no part can alter its state unexpectedly.

Aliasing occurs when multiple names, references, or pointers provide access to one object. Aliasing is useful for sharing data and avoiding copies, but it complicates reasoning. A function that receives an alias may change state observed elsewhere.

A common defensive technique is to copy at a boundary where ownership or mutation is unclear:

```python

def record_tags(tags):

saved_tags = list(tags)

saved_tags is independent of the caller's list

```

The correct policy depends on cost and meaning. Copying is appropriate when a component must own a stable snapshot. Sharing is appropriate when components intentionally collaborate on one changing object.

5. Pointers and References in C++

A pointer is a typed object that can contain the address of another object or the special value `nullptr`.

```cpp

int value = 7;

int* pointer = &value;

std::cout << *pointer; // dereference: prints 7

*pointer = 8; // changes value

```

The `&` operator obtains an address in this context, and `*` dereferences a pointer. Dereferencing `nullptr` or an invalid pointer causes undefined behavior; the language does not guarantee a controlled error.

A C++ reference is an alias:

```cpp

int value = 7;

int& reference = value;

reference = 8; // value becomes 8

```

A reference must be initialized and normally cannot be reseated to refer to a different object. A pointer can be null and can be reassigned:

```cpp

int a = 1, b = 2;

int* p = &a;

p = &b;

```

References are suitable when a valid target is required. Pointers are suitable when absence (`nullptr`), reseating, pointer arithmetic, or interaction with low-level APIs is required. Neither pointer nor reference syntax automatically conveys ownership. A raw pointer may be owning in a legacy design, non-owning in a modern design, or ambiguous; ambiguity is dangerous.

5.1 Pointer validity

A pointer is safe to dereference only when all relevant conditions hold:

1. It points to a live object.

2. Its type and alignment are appropriate.

3. Access is permitted by the object’s visibility and constness rules.

4. The memory has not been released or invalidated.

For example:

```cpp

int* bad() {

int local = 10;

return &local; // dangling pointer after return

}

```

The local object dies when the function returns. The returned address is therefore invalid. Similarly, pointers and references into a `std::vector` can be invalidated when reallocation occurs after insertion. A robust design documents invalidation rules and avoids retaining such aliases across operations that may move elements.

6. Dynamic Allocation and Ownership

Dynamic allocation is justified when an object must outlive the creating scope, has a runtime-determined size, or participates in a data structure whose nodes have independent lifetimes. It is not justified merely because an object is “large” or because heap allocation seems more flexible.

Ownership answers the question: which component is responsible for keeping an object alive and releasing its resources? Common ownership models are:

  • Exclusive ownership: exactly one owner; ownership may be transferred.
  • Shared ownership: multiple owners; destruction occurs when the last owner disappears.
  • Non-owning access: a pointer or reference may use an object but does not control its lifetime.

Modern C++ represents these models explicitly:

```cpp

std::unique_ptr<int> one = std::make_unique<int>(42);

std::unique_ptr<int> two = std::move(one); // ownership transferred

// one is now empty; two owns the integer

```

`std::unique_ptr` cannot be copied because copying would create two apparent exclusive owners. It can be moved.

```cpp

auto shared1 = std::make_shared<std::string>("data");

auto shared2 = shared1; // shared ownership

```

The object remains alive while at least one `shared_ptr` owns it. Shared ownership has overhead and can conceal complex lifetimes. It should not be the default substitute for a clear ownership design.

A `std::weak_ptr` observes an object managed by `shared_ptr` without extending its lifetime:

```cpp

std::weak_ptr<Node> observer = shared1;

if (auto locked = observer.lock()) {

// locked is a temporary shared owner while used

}

```

Weak pointers are particularly important for breaking ownership cycles.

7. Copying, Moving, and Resource Handles

Copying can mean different things. A shallow copy duplicates the outer structure while preserving references to nested objects or resources. A deep copy recursively creates independent nested data.

Python example:

```python

import copy

original = [[1, 2], [3, 4]]

shallow = copy.copy(original)

deep = copy.deepcopy(original)

shallow[0].append(9)

original[0] also changes

deep[1].append(8)

original[1] does not change

```

A shallow copy is sufficient when nested objects are immutable or intentionally shared. Deep copying can be expensive and may be semantically inappropriate for objects connected to files, sockets, locks, or external systems.

In C++, a class that owns a resource must define or correctly obtain appropriate copy and move behavior. The Rule of Three states that if a class needs a destructor, copy constructor, or copy-assignment operator, it may need all three. The Rule of Five adds move construction and move assignment. The modern Rule of Zero recommends composing classes from standard resource-managing types so that custom special member functions are unnecessary.

For example, a class containing `std::vector`, `std::string`, and `std::unique_ptr` can often rely on compiler-generated operations. A raw owning pointer complicates copying because two objects might attempt to delete the same allocation.

Move semantics transfer resources rather than duplicating them:

```cpp

std::vector<int> make_values() {

std::vector<int> result{1, 2, 3};

return result;

}

std::vector<int> values = make_values();

```

Modern C++ can construct or move the result efficiently. A moved-from object remains valid but its specific value is generally unspecified unless the type documents more precise behavior.

8. Garbage Collection and Object Reclamation

Garbage collection automatically identifies objects that can no longer be reached by a program and reclaims their memory. Python implementations commonly use reference counting supplemented by a cyclic garbage collector. When an object’s reference count reaches zero, it can often be reclaimed immediately; cyclic structures require additional detection.

```python

class Node:

pass

a = Node()

b = Node()

a.other = b

b.other = a

```

The two nodes reference one another. If no external name refers to either node, simple reference counting alone cannot reclaim them, but cycle detection can.

Garbage collection manages memory, not every resource. Files, database connections, sockets, locks, and operating-system handles should be released deterministically. Python uses context managers:

```python

with open("report.txt") as file:

data = file.read()

file is closed when the with block exits

```

C++ generally uses deterministic destruction rather than tracing garbage collection. This distinction is central: a C++ object’s destructor runs when its lifetime ends, usually at scope exit or when its owning smart pointer is destroyed. Python’s `__del__` method should not be treated as a dependable replacement for a context manager, especially in the presence of cycles or interpreter shutdown.

9. RAII: Resource Acquisition Is Initialization

RAII is a C++ design principle in which a resource is acquired during object construction and released during destruction. Since destruction occurs automatically when an object leaves scope, RAII makes cleanup reliable even when exceptions are thrown.

```cpp

void save() {

std::ofstream output("data.txt");

if (!output) {

throw std::runtime_error("cannot open file");

}

output << "content\n";

} // output's destructor closes the file

```

The same pattern applies to memory, mutex locks, sockets, and transactions. Standard library types such as `std::lock_guard`, `std::scoped_lock`, `std::fstream`, `std::vector`, and smart pointers embody RAII.

RAII is stronger than relying on programmers to remember cleanup along every control-flow path. It also improves exception safety. If an exception leaves a scope, destructors for already-created local objects are invoked in reverse order of construction.

Python’s `with` statement expresses a closely related deterministic cleanup protocol through context managers. The connection is conceptual rather than identical: C++ uses destruction and scope, while Python uses the context-manager protocol.

10. Common Failure Modes and Prevention

Memory leaks

A leak occurs when allocated memory remains unavailable because the program has lost the means to release it. In C++, use containers and `make_unique` or `make_shared`; avoid owning raw pointers. In Python, retained references, global caches, unbounded collections, and reference cycles involving external resources can create practical memory growth even when garbage collection is present.

Dangling pointers and references

These refer to objects whose lifetime has ended. Avoid returning references to local variables, retaining pointers into invalidated containers, and storing references to temporary objects. Use ownership types and clear lifetime boundaries.

Double deletion

This occurs when two owners release the same allocation. It is prevented by unique ownership types, correct move-only design, and avoiding manual `delete` on memory managed by a smart pointer.

Null and uninitialized pointers

Always initialize pointers. Use `nullptr`, not an arbitrary integer or legacy `NULL`. Check optional pointers before dereferencing, or use a type such as `std::optional` when the issue is an optional value rather than an address.

Unintended aliasing

Document whether a function borrows, shares, or takes ownership. In Python, copy mutable inputs when a stable private collection is required. In C++, use `const&` for non-owning read-only access and value types when independent state is intended.

Cycles

Shared ownership cycles prevent reference-counted reclamation. Design ownership as a directed structure where possible, and use `weak_ptr` for back-links or observers. In Python, context managers and explicit lifecycle design remain important even though cyclic garbage may be collected.

11. Worked Example: Designing a Safe Message Queue

Suppose a service receives messages and passes them from a producer to a consumer. A naive design might allocate each message with `new`, place raw pointers into a queue, and require the consumer to delete them. This design leaves several questions unanswered: Who deletes a message if processing fails? What happens if the consumer exits? Can two components delete the same message?

A safer C++ design uses value semantics or exclusive ownership. If messages are small and movable:

```cpp

std::queue<Message> queue;

queue.push(Message{"status", 200});

Message next = std::move(queue.front());

queue.pop();

```

If polymorphism or large variable-sized objects requires dynamic allocation:

```cpp

std::queue<std::unique_ptr<Message>> queue;

queue.push(std::make_unique<Message>("status", 200));

std::unique_ptr<Message> next = std::move(queue.front());

queue.pop();

```

The queue owns each message until ownership is moved to `next`. If processing throws, `next` still destroys the message automatically when it leaves scope. No manual deletion is needed.

In Python, the queue normally stores object references:

```python

from queue import Queue

messages = Queue()

message = {"kind": "status", "code": 200}

messages.put(message)

received = messages.get()

```

The queue and producer may share the same dictionary. If the producer must not alter a message after submission, it should submit an immutable representation or a copy, such as `dict(message)` for a shallow copy. The choice depends on whether nested values are also mutable.

12. Illustrative Case: A Web-Service Memory Incident

Background. A fictional web application stores recently viewed products in a process-wide Python dictionary for faster responses.

Problem. Memory usage grows continuously. Investigation finds that each request adds product objects to a cache, but old entries are never removed. Some product objects also contain references to request-specific data, keeping large graphs reachable.

Analysis. Python’s garbage collector cannot reclaim an object that remains reachable from the global cache. The issue is not a failure of garbage collection; it is an ownership and retention-policy failure. The cache has effectively become the owner of every object ever inserted.

Approach. Engineers establish a bounded cache with an eviction policy and store compact immutable product records rather than complete request graphs. They add metrics for cache size, hit rate, and process memory. Request-specific objects are not retained after the response. For resources such as files and database cursors, context managers guarantee timely release.

Outcome. Memory becomes bounded by the cache policy, and object lifetimes match business requirements. The team documents which component owns cached data and when it may be discarded.

Learning points. Automatic memory management does not eliminate lifecycle design. Reachability, ownership, mutability, and retention policy must be considered together. Similar reasoning applies in C++, where a global container of owning smart pointers can also retain objects indefinitely.

13. Professional Practice, Security, and Contemporary Tools

Memory safety is an employability skill because production failures often arise at component boundaries. Developers should understand API contracts, ownership annotations, exception behavior, and invalidation rules. Code review should ask who owns each resource and whether every lifetime path is safe.

Memory errors can become security vulnerabilities. Use-after-free, buffer overflows, double frees, and invalid casts may permit data disclosure or arbitrary code execution. C++ projects should use sanitizers such as AddressSanitizer and UndefinedBehaviorSanitizer during testing, static analysis, compiler warnings, and fuzz testing. Python projects benefit from profiling tools, leak detection, bounded caches, and tests for resource closure.

AI-assisted programming tools can suggest pointer code or resource-management patterns, but generated code must be reviewed. A plausible-looking raw pointer solution may omit ownership, exception safety, or invalidation concerns. Developers remain responsible for verifying lifetime assumptions and testing failure paths.

Contemporary C++ practice favors value types, RAII, standard containers, smart pointers, move semantics, and explicit non-owning views. Contemporary Python practice favors context managers, immutable data where practical, clear copying policies, bounded caches, and avoiding reliance on finalizers for critical cleanup.

Best-Practice Checklist

  • Prefer values and automatic scope-based lifetime when possible.
  • Give every owning resource one clearly identifiable owner.
  • Prefer `std::unique_ptr` for exclusive dynamic ownership.
  • Use `std::shared_ptr` only when shared lifetime is genuinely required.
  • Use `std::weak_ptr` for non-owning observation and cycle prevention.
  • Avoid manual `new` and `delete` in application-level C++ code.
  • Treat raw pointers and references as non-owning unless documentation states otherwise.
  • In Python, distinguish binding, mutation, and copying.
  • Use context managers for files, locks, and other external resources.
  • Be cautious with shallow copies of nested mutable structures.
  • Test exception paths and early returns, not only successful execution.
  • Use sanitizers, static analysis, profilers, and code review to find lifetime defects.

Challenges and Limitations

No single memory model solves every problem. Garbage collection reduces explicit deallocation errors but may introduce pauses, retention surprises, or non-deterministic reclamation. Reference counting offers prompt reclamation in many cases but requires cycle handling and incurs bookkeeping overhead. Manual allocation can provide control but creates substantial correctness risk.

Smart pointers solve ownership of many heap objects, but they do not automatically solve data races, logical leaks caused by long-lived owners, invalidated iterators, or incorrect synchronization. Copying can prevent aliasing but may be expensive. Sharing can improve performance but makes mutation and lifetime harder to reason about.

The correct engineering decision therefore combines language rules, performance requirements, resource type, concurrency model, and API meaning. Memory management is not merely a choice between stack and heap; it is the design of object lifetime and responsibility.

Chapter Summary

Stack and heap are practical storage concepts, while C++ storage duration and object lifetime provide more precise language-level descriptions. Names, pointers, and references provide access to objects but do not necessarily own them. Value semantics create independent state; reference semantics share identity. Mutability makes unintended aliasing especially important.

Python assignment usually binds another name to an existing object, and garbage collection manages unreachable memory, but context managers are required for deterministic external-resource cleanup. C++ offers direct pointers and references, yet modern programs should express ownership through RAII, standard containers, and smart pointers. `unique_ptr` models exclusive ownership, `shared_ptr` models shared ownership, and `weak_ptr` provides non-owning observation.

Copying, moving, ownership, and lifetime must be considered together. Leaks, dangling references, double deletion, invalidated pointers, cycles, and unintended aliasing are preventable through clear contracts, scope-based cleanup, safe abstractions, testing tools, and disciplined design.

Key Terms

Object identity: The property distinguishing one object from another, even when their values are equal.

Value semantics: Behavior in which copying produces an independent logical value.

Reference semantics: Behavior in which copied access refers to shared underlying state.

Mutability: The ability of an object to change state after creation.

Aliasing: The existence of multiple access paths to the same object.

Stack: A commonly used region supporting nested function calls and automatic lifetimes.

Heap: A commonly used region for dynamically obtained storage.

Automatic storage duration: C++ lifetime controlled by scope and normally ended at scope exit.

Dynamic storage duration: C++ lifetime continuing until dynamically allocated storage is released.

Pointer: A C++ object that stores an address or `nullptr`.

Reference: A C++ alias bound to an existing object.

Dangling pointer: A pointer whose target no longer exists or is no longer valid.

Ownership: Responsibility for maintaining an object and releasing its resources.

Memory leak: Allocated memory that remains unavailable because it cannot be reclaimed.

Garbage collection: Automatic reclamation of unreachable objects.

RAII: Resource Acquisition Is Initialization; resource release through deterministic object destruction.

Smart pointer: A C++ library type that manages ownership or observation of dynamically allocated objects.

Shallow copy: A copy that may continue sharing nested objects or resources.

Deep copy: A copy that recursively creates independent nested state.

Move semantics: Transfer of resources from one object to another without necessarily duplicating them.

Review Questions

Short Answer Questions

1. Distinguish object identity from value equality.

2. What is unintended aliasing, and why is it risky for mutable objects?

3. State one important difference between a C++ pointer and a C++ reference.

4. Why does garbage collection not replace context managers for files and sockets?

5. What ownership model does `std::unique_ptr` represent?

Long Answer Questions

1. Compare stack and heap storage, including their relationship to lifetime and storage duration.

2. Explain Python assignment, mutability, shallow copying, and deep copying with examples.

3. Describe how RAII provides exception-safe resource management in C++.

4. Compare `unique_ptr`, `shared_ptr`, and `weak_ptr`, including appropriate use cases.

5. Explain the causes and prevention of leaks, dangling references, and double deletion.

Analytical / Application Questions

1. A function returns a pointer to one of its local variables. Analyze the defect and propose two safe alternatives.

2. A Python service’s memory usage grows although garbage collection is enabled. Use reachability and ownership concepts to identify possible causes and propose an investigation plan.

3. Design an ownership model for a polymorphic message queue in C++. Explain whether values, `unique_ptr`, or `shared_ptr` should be used and justify your choice.

Multiple Choice Questions with Answers

Question 1

What does Python assignment generally do when assigning one list variable to another?

A. Creates a deep copy of the list

B. Binds another name to the same list object

C. Converts the list into a tuple

D. Moves the list and invalidates the original name

Correct Answer: B. Binds another name to the same list object

Question 2

Which C++ type is designed primarily for exclusive ownership of a dynamically allocated object?

A. std::weak_ptr

B. std::shared_ptr

C. std::unique_ptr

D. std::reference_wrapper

Correct Answer: C. std::unique_ptr

Question 3

What is the main danger of returning the address of a local C++ variable?

A. The variable becomes immutable

B. The returned pointer may be dangling after the function returns

C. The pointer automatically becomes a smart pointer

D. The local variable is copied into the heap

Correct Answer: B. The returned pointer may be dangling after the function returns

Question 4

Which operator tests object identity in Python?

A. ==

B. =

C. is

D. in

Correct Answer: C. is

Question 5

What does RAII use to release a C++ resource reliably at scope exit?

A. A global variable

B. An object destructor

C. A Python garbage collector

D. Manual pointer arithmetic

Correct Answer: B. An object destructor

Question 6

Why is std::weak_ptr useful in a shared-ownership design?

A. It creates a second owning reference

B. It prevents all object destruction

C. It observes an object without extending its lifetime

D. It automatically copies the managed object

Correct Answer: C. It observes an object without extending its lifetime

Question 7

What happens when a mutable Python list is assigned to a second variable and the second variable appends an item?

A. Only the second variable changes

B. Both names observe the changed list

C. Both variables become tuples

D. The original list is automatically deep-copied

Correct Answer: B. Both names observe the changed list

Question 8

Which practice best reduces manual memory-management errors in modern C++?

A. Use raw new and delete in every function

B. Store owning addresses as integers

C. Prefer standard containers and RAII-based smart pointers

D. Return pointers to local variables

Correct Answer: C. Prefer standard containers and RAII-based smart pointers

Question 9

What is a shallow copy of a nested structure likely to preserve?

A. Sharing of nested mutable objects

B. Automatic conversion to immutable data

C. A new independent copy at every level

D. The destruction of the original structure

Correct Answer: A. Sharing of nested mutable objects

Question 10

Which statement about garbage collection is correct?

A. It guarantees immediate closure of every file

B. It reclaims objects that are no longer reachable, but external resources still need explicit lifecycle management

C. It prevents all memory growth caused by global caches

D. It makes dangling C++ references safe

Correct Answer: B. It reclaims objects that are no longer reachable, but external resources still need explicit lifecycle management

Programming Fundamentals (Python/C++)
Chapter 7 of 15