CS 342: C++ Memory Management
Overview of C++ Memory Management
4 major memory segments
Global: variables outside stack, heap Code (a.k.a. text): the compiled program Heap: dynamically allocated variables Stack: parameters, automatic and temporary variables
global code heap
Key differences from Java
Destructors of automatic variables called when stack frame where declared pops No garbage collection: program must explicitly free dynamic memory
Heap and stack use vary dynamically Code and global use is fixed Code segment is read-only
stack
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Illustrating C++ Memory
int default_value = 1; int main (int argc, char **argv) { Calculator *d = new Calculator; d -> set_value (default_value); return 0; }
int default_value
global code heap
int value_ Calculator
void Calculator::set_value (int i) { Calculator * this value_ = i; Calculator::set_value int i }
stack
main crt0
Calculator * d
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Illustrating C++ Memory, cont.
int default_value = 1; int main (int argc, char **argv) { Calculator *d = new Calculator; d -> set_value (default_value); return 0; }
int default_value
global code heap
int value_ Calculator
void Calculator::set_value (int i) { Calculator * this value_ = i; Calculator::set_value int i }
stack
main crt0
Calculator * d
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
A Basic Issue: Aliasing
int main (int argc, char **argv) { Calculator c; Calculator * d = & c; Calculator & e = c; delete d; return 0; }
Multiple aliases for same object
c is a simple alias, the object itself d is a variable holding a pointer e is a variable holding a reference
What happens when we call delete on d?
Destroy a stack variable (may get a bus error there if were lucky) If not, we may crash in destructor of c at function exit Or worse, a local stack corruption that may lead to problems later
Calculator &e Calculator *d
Problem: object destroyed but Calculator c another alias to it was then used
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Memory Initialization Errors
int main (int argc, char **argv) { Calculator *d; cout << d->get_value (); for (int i; i < argc; ++i) cout << argv [i]; return 0; }
Heap and stack memory is not initialized for us in C++
Get whatever was out in memory Unless we initialize it ourselves So we should always do so
What happens when we dereference pointer d?
If were lucky, a program crash If not, we get bad output Or worse, a local over-write leading to problems later
Is there anything else wrong with this code?
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Calculator * bad () { Calculator c; return & c; } Calculator & also_bad () { Calculator c; return c; } Calculator mediocre () { Calculator c; return c; } Calculator * good () { return new Calculator; }
Memory Lifetime Errors
Automatic variables
Are destroyed on function return But in bad, we return a pointer to a variable that no longer exists Reference from also_bad similar Like an un-initialized pointer
What if we returned a copy?
Ok, we avoid the bad pointer, and end up with an actual object But we do twice the work (why?) And, its a temporary variable (more on this next)
We really want dynamic allocation here
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Memory Lifetime Errors, cont.
int main (int argc, char **argv) { Calculator *c = & mediocre (); cout << good () -> get_value (); cout << c -> get_value (); } Calculator mediocre () { Calculator c; return c; } Calculator * good () { return new Calculator; }
Dynamically allocated variables
Are not garbage collected But are lost if no one refers to them: called a memory leak
Temporary variables
Are destroyed at end of statement Similar to problems w/ automatics
Can you spot 2 problems?
One with a temporary variable One with dynamic allocation
Important intuition
Incorrect use is the real problem here Need to understand the details
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Memory Lifetime Errors, cont.
int main (int argc, char **argv) { Calculator *c = good (); cout << mediocre ().get_value (); cout << c -> get_value (); delete c; } Calculator mediocre () { Calculator c; return c; } Calculator * good () { return new Calculator; }
Dynamically allocated variables
Should be referenced somehow And the reference used to destroy them when finished with them
Temporary variables
Can be used throughout a statement But should not be referenced outside the statement where created Use the temporary variable to do output, etc. within a statement Use dynamic variables across scopes
Especially for use outside the creating stack frame
Example resolved
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
A More General View: Scopes
Temporary variables
Are scoped to an expression, e.g., a = b + 3 * c;
Automatic variables
Are scoped to the duration of the function in which they are declared
Dynamically allocated variables
Are scoped from explicit creation (new) to explicit destruction (delete)
Global variables
Are scoped to the entire lifetime of the program Includes static class and namespace members May still have initialization ordering issues (more later: Singleton Pattern)
Member variables
Are scoped to the lifetime of the object within which they reside Depend on whether object itself is global, dynamic, automatic, temporary
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Scopes of Variables vs. References
Variables and the references to them may have different scopes
May result in obvious kinds of lifetime errors we saw earlier But may also introduce more subtle issues of aliasing Still can risk destroying an object too soon or too late
May want to tie different kinds of scopes together
For example, freeing dynamic memory when an exception is thrown Or when an object owning another object is destroyed
Solution in C++ is a set of idioms
Constructor allocates, destructor de-allocates Guard: ties dynamic resource scopes to automatic scopes Shallow copy, deep copy semantics Reference counting: ties dynamic lifetime to a group of references Copy-on-write: allows more efficient management of multiple aliasing
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Ctor Allocates, Dtor Deallocates
#if ! defined (CALCULATOR_H) #define CALCULATOR_H #define DEFAULT_SIZE 20 class Calculator { public: Calculator (size_t s = DEFAULT_SIZE); ~Calculator (); private: size_t stack_size_; int * values_; }; #endif /* CALCULATOR_H */
Resources allocated by an object need to be freed Constructor, destructor offer good start/end points Fixed stack calculator
Can push and pop ints Can add, etc. to top of stack Stack size may differ for each object Once set, stack size is fixed Notice the default parameter
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Ctor Allocates, Dtor Deallocates, cont.
#include "Calculator.h" Calculator:: Calculator (size_t s) : stack_size_ (s), values_ (0) { if (stack_size_ > 0) { values_ = new int [stack_size_]; if (values_ == 0) stack_size_ = 0; } } // ...
Calculator initialization sets size, but zeroes out the pointer why? Test Only allocate if length is greater than zero Could also use conditional operator at initialization:
values_ (s > 0 ? new int [s] : 0)
Which is way is better? What does new do?
Allocates memory Calls constructor(s)
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Ctor Allocates, Dtor Deallocates, cont.
// ... Calculator::~Calculator () { delete [] values_; } int main (int argc, char** argv) { Calculator c; // When c is created, it // allocates memory return 0; // Now when c is destroyed, so // is the memory it allocated }
Destructor releases the dynamic array So, scopes are tied
Calculator and its stack
What does delete do?
Calls destructor(s) Frees memory
What are new [] and delete []
vs. plain old new and delete
What if values_ was 0 when delete was called?
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Applying Guard: Function Scope
Calculator * create_and_init () { Calculator *c = new Calculator; auto_ptr<Calculator> p (c); init (c); // may throw exception p.release (); return c; } int run () { try { Calculator *d = create_and_init (); } catch (...) {} // ... }
Guard idiom revisited C++ has an auto_ptr class template auto_ptr<X> assumes ownership of a pointer-to-X auto_ptr destructor calls delete on the owned pointer Call release to break ownership by auto_ptr when its safe to do so
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Copy Constructor: Shallow
// ... Calculator::Calculator (const Calculator & c) : stack_size_ (c.stack_size_), values_ (c. values_) { }
Two ways to copy
Shallow: aliases existing resources (i.e., initially) Deep: makes a complete and separate copy
Version to left shows shallow copy
Efficient But may be risky: why?
Whats the invariant?
values_, stack_size_
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Copy Constructor: Deep
Calculator::Calculator (const Calculator & c) : stack_size_ (c.stack_size_), values_ (0) { if (stack_size_ > 0) { values_ = new int [stack_size_]; if (values_ == 0) stack_size_ = 0; for (size_t s = 0; s < stack_size_; ++s) values_[s] = c.values_[s]; } }
Version to left shows deep copy
Safe: no aliasing But may not be very efficient I.e., what if its reasonable to share the resource n times?
Note trade-offs with arrays
Allocate memory once More efficient than multiple calls to new (heap search) Constructor and assignment called on each array element Less efficient than block copy But sometimes more correct
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Intro to Reference Counting
Basic Problem
Resource sharing is efficient But hard to tell when done Must avoid early deletion Must avoid leaks
reference
Solution reference resource = hi counter = = 3 reference
Share resource and a counter Each new reference increments the counter When a reference is done, it decrements the counter When count drops to zero, delete resource and counter
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Intro to Copy on Write
Basic Problem resource = lo reference counter = = 1
Reference counting enables safe and efficient sharing But what about modifications? May want logically separate copies of resource
Solution reference resource = hi counter = = 2 reference
Start with reference counting Writer checks for count > 1
Copies reference & counter Updates both counters Performs the write
Readers all share a copy, each writer can get its own
Copyright 2002 Dept. of Computer Science and Engineering, Washington University
CS 342: C++ Memory Management
Summary: Memory Management Tips
Know what goes where in memory Understand mechanics of stack, dynamic allocation Watch for simple lifetime errors Consider more subtle scope/lifetime issues Pay attention to aliasing (draw a picture) Think about shallow versus deep copying trade-offs Master useful idioms for C++ memory management
Learn how they work Understand when to apply them Look for ways to apply them in the labs and beyond
Copyright 2002 Dept. of Computer Science and Engineering, Washington University