Memory, References, Pointers, and Resource ManagementIntroduction and ContextPrograms 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 ObjectivesBy 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 NamesA 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 lifetimeThree 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 StorageThe 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 storageThe 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 storageThe 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 locationC++ 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 SemanticsValue 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 argumentsPython 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 AliasingAn 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 immutabletext = "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 validityA 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 OwnershipDynamic 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 HandlesCopying 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 changesdeep[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 ReclamationGarbage 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 InitializationRAII 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 PreventionMemory leaksA 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 referencesThese 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 deletionThis 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 pointersAlways 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 aliasingDocument 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. CyclesShared 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 QueueSuppose 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 IncidentBackground. 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 ToolsMemory 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 LimitationsNo 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 SummaryStack 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 TermsObject 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 QuestionsShort Answer Questions1. 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 Questions1. 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 Questions1. 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 AnswersQuestion 1What 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 2Which 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 3What 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 4Which operator tests object identity in Python? A. == B. = C. is D. in Correct Answer: C. is Question 5What 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 6Why 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 7What 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 8Which 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 9What 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 10Which 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
|