390 likes | 520 Views
CSE 143. Dynamic Memory [Chapter 7]. Dynamic Memory: Intro. Problem: All of our data structures so far have a “maximum” size. This size is fixed at compile time .
E N D
CSE 143 Dynamic Memory [Chapter 7]
Dynamic Memory: Intro • Problem: All of our data structures so far have a “maximum” size. • This size is fixed at compiletime. • Often this is acceptable, but many real applications need to grow and shrink the amount of memory consumed by an ADT at run time. • Most languages provide some form of dynamic memory. • C++ provides an interface to dynamic memory via two new operators: new and delete.
Data and Memory • Objects of different types may requires differing amounts of memory • Built-in types: implementation dependent • PC (typical): • char: 1 byte (8 bits) • int: 4 bytes (2 bytes on older systems) • double: 8 bytes • Programmer defined types: depends on size of data members • could be few bytes or thousands of bytes
Ways of Using Memory • Static - allocated at program startup time, exists throughout the execution of the entire program • Automatic - implicitly allocated upon function entry, deallocated on exit • void foo (char x) { • int temp; • . . . • // x and temp are deallocated here • } • Dynamic - explicitly allocated and deallocated by the programmer
Pointers: Review • Every memory cell in a computer’s memory has two characteristics: • A value (contents) • A unique address • Every object in the program occupies a range of memory cells • Suppose we have: • int x; • To access the value of x: • x • To access the address of x: • &x
Pointer Variables • By "address of an object" we mean the address of the first memory cell used by the object • A pointer variable is one that contains the address of another data object as its value. • To declare a pointer variable: • Type* name; • Example: • int* intPtr; • char* charPtr; • BigNat* bigNatPtr;
Pointers and Types • Pointers to different types themselves are different types double *dpt; BankAccount * bp; • C/C++ considers dpt and bp to have different types • even though under the hood they are both just memory addresses • Types have to match in many contexts • e.g. actual param types matching formal param types • pointers are no exceptions
Two Important Operators • The address-of operator &: • int x = 45; • int* p = &x; • The dereference operator *: • *p = 30; • p = 72; // what’s the problem here?
The NULL pointer • During program execution, a pointer variable can be in one of the following states: • Unassigned • Pointing to a data object • Contain the special value NULL • The constant NULL is defined as 0 in stddef.h, and is used to mean a pointer that does not point to any object. • NULL is compatible with all pointer types • Style factoid: some programmers like NULL, others use 0.
Pointer Types & Ops • Domain (possible values) • The set of all memory addresses along with the NULL pointer • Operations valid on pointers of all types (pp292-293). We’ll cover only a subset: = (assignment) • int* p = &someInt; * (dereference) • *p = 345; == (equality test) • if (ptr1 == ptr2) { . . . }
More Pointer Operations != (test for inequality) • if (ptr1 != ptr2) { . . . } delete (deallocate) • delete ptr; // more on this later -> (select a member of a pointed-to object) • void foo (BankAccount* b) { • b->printBalance(); • }
Reference Types • We can declare variables or params of reference types: • Type& rname //rname will hold an alias to something • //of type Type • Example: • int x; • int& refx = x; // a ref. variable must be initialized • x = 40; • cout << refx; // what’s the output? • refx = 20; • cout << x; // what’s the output? • Main use: for parameters
Writing Swap (Old School) • In 142, you used pointers to write functions which side-effected their actual parameters: • void swap(int* p, int* q) { • int temp; • temp = *p; • *p = *q; • *q = temp; • } • // example call: • swap(&intOne, &intTwo); // don’t forget the &
Writing Swap (New School) • C++ lets us use reference parameters, leading to much cleaner code: • void swap(int& a, int& b) { • int temp = a; • a = b; • b = temp; • } • // example call: • swap(intOne, intTwo); // note: no &
Allocation & Deallocation • Allocate dynamic memory with the new operator: • The expression new Type returns a pointer to a newly created object of type Type: • int* p; • char* str; • p = new int; // allocate a single int • str = new char[10]; // allocate an array of chars • Deallocate memory with the delete operator: • delete Pointer deallocates the object pointed to by Pointer • delete p; // deallocating a simple object • delete [] str; // deallocating an array of objects
The Heap • Objects created by new come from a region of memory set aside for dynamic objects (called the heap, or free store). • The new operator obtains a chunk of memory from the heap; delete returns that memory to the heap. • In C++ the programmer must manage the heap. • Dynamic data is unnamed and can only be accessed through pointers.
Two Pesky Problems • Inaccessible objects and memory leaks • An inaccessible object is a dynamic object without any pointers pointing to it • Sometimes called garbage. • Accumulating garbage leads to a memory leak. • Dangling pointers • A pointer that points to where an object used to be (even though the object's memory has been deallocated) is a dangling pointer. • Both are serious problems • dangling pointer is especially dangerous
Garbage (memory leak) • Example • int* p; • p = new int; • *p = 45; • p = new int; //! • *p = 55; • Example 2 • int *p, *q; • p = new int; • q = new int; • *p = 45; • *q = 55; • p = q; //! local vars. heap p p q
Dangling Pointers • Example 1 • int *p = new int; • int *q; • *p = 45; • q = p; • delete p; • *q = 55; // oops! • Example 2 • char* broken() { • char buffer[80]; • cin >> buffer; • return buffer; • } • charPtr = broken(); // intPtr is dangling local vars. heap p q Destroyed when function exits!!
Dynamic Data Guidelines • Avoid creating garbage when invoking new or moving pointers. • The new operator returns a pointer to an object. If the object’s type is a class, then the constructor has been called. If not, the object is still uninitialized. • Don’t dereference an unassigned pointer. • After delete, the pointer value is unassigned (don't assume NULL)
Example for DM: A Vector Class • In C++, built-in arrays have several shortcomings: • When declaring an array, the programmer must state its size in advance. • Out-of-bounds subscripts are not detected at run-time. • int v[20]; • v[i] = 100; // big oops if i is out of bounds. • There is no aggregate assignment of one array to another: • int a[20], b[20]; • a = b; // error • Arrays can’t be returned as function values. • These can be overcome • by defining our own C++ class with the desired behavior.
A Vector Class • We would like to have a class that allows: • cout << “input array size”; • cin >> n; • IntVector u(10); // a 10 element "array" • IntVector v(n); // an n element array • IntVector w(n); • u[2] = v[3]; //normal array [] notation • w = v; //assign entire vector
First Try • class IntVector { • public: • IntVector(int n); // constructor • int& operator[](int i); // subscript • private: • int *vec; • int size; • };
Basic Constructor • IntVector::IntVector(int n) { • assert (n>0); • size = n; • vec = new int[size]; • }
Overloading [ ] • Clients want to write code like this: • IntVector a(3); • IntVector b(4); • a[2] = b[3]; • Need to overload subscripting: • int& • IntVector::operator[] (int i) { • assert(i>=0 && i<size); // range check • return vec[i]; • } • Why do we return a reference to integer?
Problem 1 (Shallow copying) • We can now write: • IntVector v1(5); • IntVector v2(5); • v1[1] = 100; • v2[3] = v1[0] = v1[1]; • Now suppose: • v1 = v2; // what happens??
Shallow Copying • The default assignment operator (=), just performs memberwise assignment • This results in a shallow copy • Here is a picture just before v1 = v2: heap space: v1 v2
Problems w/ Shallow copies • Shallow copying causes instances to share data, which may be undesirable: • Vector v1(10); • Vector v2(10); • v2 = v1; • v1[3] = 17; // what does v2[3] contain now? • Deep copying gives each instance its own copy: • void IntVector::operator=(IntVector& other) • { • Destroy(); // free my dynamic data • Copy(other); // get a copy of other’s data • }
Private Helper Functions • Unconditionally copy the other vector: • void IntVector::Copy(IntVector& other) { • vec = new int[other.size]; • size = other.size; • for (int i=0; i<size; i++) • vec[i] = other.vec[i]; • } • Unconditionally deallocate data: • void IntVector::Destroy() { • delete [] vec; • vec = NULL; • }
Safer Assignment • Self-assignment is a problem. Why? • v1 = v1; // shouldn’t self assign… • Ever object has a special data member, a pointer called this, which is a pointer to the object itself. • void IntVector::operator=(IntVector& other) • { • if (this != &other) { • Destroy(); // free my dynamic data • Copy(other); // get a copy of other’s data • } • }
Destructors & Memory Leaks • A destructor is a special method automatically invoked when the class instance is destroyed. • A class instance is destroyed when it goes out of scope: • void fun (IntVector v1) { • IntVector v2(10); • . . . • } // v1 and v2 are destroyed here • What happens to the private data of v1 and v2? • What happens to the memory pointed to by v1 and v2?
Problems • Automatic data • automatically deallocated at end of block (e.g. when a function exits.) • no problem • Heap space, however, is managed by the programmer. • never automatically deallocated • If we don’t free the memory pointed to by our object, we’ll get a memory leak.
Destructors (3) • We can write our own destructor which frees the memory our object points to: • IntVector::~IntVector() { • Destroy(); • } • There should be only one destructor per class. • The destructor takes no arguments. • Use [ ] for an array of instances, to assure that a destructor is called for each element.
Problem 2 (More Shallow Copying) • Suppose: • void DoSomething(IntVector v1) { • . . . • } • . . . • IntVector v(10); • DoSomething(v); • When we pass an object to a function, the formal parameter is a shallow copy of the actual parameter. • Can you see why this code is broken? (Hint: think about the destructor we just wrote!)
Copy constructors • The same problem with deep vs. shallow copying can also appear in this context: • Initialization in a variable declaration: • SomeClass foo = bar; • Passing a copy of an actual to a formal parameter (pass-by-value) • Returning an instance as the value of a function: • return someIntVector; • By default, C++ performs such initializations using shallow copy semantics.
Copy constructors (2) • To define your own way of copying, you can write a copy constructor, which has a special syntax: • // General form of copy constructor: • SomeClass::SomeClass(SomeClass& other); • IntVector::IntVector(IntVector& other) { • Copy(other); • } • Now when we pass-by-value, the formal parameter will be deep copy of the actual. • Because compiler will call the copy constructor instead of doing a shallow copy
Full IntVector Specification • class IntVector { • public: • IntVector(int n); // contstructor • IntVector(IntVector& other); // copy cons • ~IntVector(); // destructor • int& operator[](int i); • void operator=(IntVector& other); • private: • int* vec; • int size; • void Copy(IntVector& other); • void Destroy(); • }
Classes w/ dynamic data • If a class allocates data on the heap, it probably needs the following suite of member functions to ensure proper management of dynamic data: • constructor - to create data on the free store • copy constructor - for deep copying during initialization and pass-by-value • destructor - to release memory back to the free store • assignment operator - for deep copying during assignment
Example Client Code • #include “IntList.h” • void main() { • int size; • cout << “Input size”; • cin >> size; • IntVector v1(size),v2; • for (int i=0; i<size; i++) • v1[i] = i; • v2 = v1; // make a copy • for (int j=0; j<size; j++) • cout << v2[j] << “,”; • }