diff --git a/labs/14_smart_memory/README.md b/labs/14_smart_memory/README.md new file mode 100644 index 0000000..9969cf4 --- /dev/null +++ b/labs/14_smart_memory/README.md @@ -0,0 +1,68 @@ +# Lab 14 — Garbage Collection & Smart Pointers + +## Checkpoint 1: + +*estimate: 20-40 minutes* + +For the first checkpoint, download, compile, and run these files: [stop_and_copy.h](stop_and_copy.h), [stop_and_copy.cpp](stop_and_copy.cpp), [main_stop_and_copy.cpp](main_stop_and_copy.cpp) + +In Lecture 25, we stepped through the Stop and Copy garbage collection algorithm on a small example. +Examine the output of the program to see a computer simulation of this same example. Verify that the +program behaves as we predicted in lecture. + +Continuing with the same example, 3 more nodes have been added and the garbage collector must be run +again. Draw a “box and pointer” diagram (the more usual human-friendly version of the interconnected +node structure with few or no crossing pointer arrows) of the memory accessible from the root pointer after +these 3 nodes are added and work through the Stop and Copy algorithm for this example on paper. When +you are finished, uncomment the simulation and check your work. + +**To complete this checkpoint**: Show (and explain to) one of the TAs your box and pointer diagram. + +## Checkpoint 2: + +*estimate: 20-40 minutes* + +The theme for this checkpoint are the helium filled balloons for the Macy’s Thanksgiving Day parade. These +balloons are held in place by one or more ropes held by people on the ground. Alternately, balloons may +be connected to other balloons that are held by people! People can swap which balloon they are holding +on to, but if everyone holding on to the ropes for a particular balloon lets go, we will have a big problem! +Download, compile, and run these files: [ds_smart_pointers.h](ds_smart_pointers.h) and [main_smart_pointers.cpp](main_smart_pointers.cpp). +Use Dr. Memory or Valgrind to inspect the initial code for memory errors and/or memory leaks. + +Carefully examine the example allocations in the main function of the provided code. Draw simple pictures +to help keep track of which objects have been allocated with new, and which variables currently point to +each dynamically allocated object. + +To fix the memory leaks in this program, you will need to add explicit deallocation for the non-smart pointer +examples (marked CHECKPOINT 2A and 2B). For comparison, the code includes simple examples of smart +pointers for these objects as well! When we know that just one person will hold on to a Balloon at a time +(one owner) we can use a dsAutoPtr (see also the STL auto_ptr or STL unique_ptr, depending on your +version of g++/clang/STL). When multiple people might hold ropes to the same Balloon, we should use a +dsSharedPtr (see also STL shared_ptr or Boost shared_ptr). A shared pointer uses reference counting! +When the last person disconnects from a Balloon using a shared pointer, the Balloon is automatically deleted. + +Re-compile & re-run with the memory debugger to confirm you have fixed the simple leaks. For the final +piece of this checkpoint (marked CHECKPOINT 2C), you must also re-rewrite the interconnected balloon +example to use shared pointers. You will need to modify the Balloon class to use dsSharedPointer as well. + +**To complete this checkpoint**: Explain to your TA the code you needed to add and/or modify to correct +the memory leaks in the provided code. Show your TA the result of the memory debugger on your finished +implementation. + +## Checkpoint 3: + +*estimate: 20-40 minutes* + +For the last checkpoint, let’s consider cyclic balloon structures. A simple example is included at the bottom +of main_smart_pointer.cpp. Draw a diagram of this structure on paper. The provided code has memory +leaks for this example. We could try to re-write this example to use the shared smart pointer. However, a +reference counting smart pointer will still have a problem on this cyclic example. Why? + +Instead, let’s write a helper function to explicitly deallocate a general cyclic structure of Balloon objects. (We +will not use smart pointers for this checkpoint). You should switch back to the original Balloon class (if you +modified it for Checkpoint 2). You will first need to collect all nodes that are reachable from the provided +argument. Make sure that you do not enter an infinite loop when tracing through the structure! You may +find std::set helpful. Once you have identified all of the nodes that are accessible from the input argument, +you can call delete on each Node. Write additional test cases to confirm that your code is debugged. + +**To complete this checkpoint**: Show the TA cyclic memory diagram, your debugged implementation, and the memory debugger output to confirm that your code has no memory errors or leaks. diff --git a/labs/14_smart_memory/ds_smart_pointers.h b/labs/14_smart_memory/ds_smart_pointers.h new file mode 100644 index 0000000..7f19b7a --- /dev/null +++ b/labs/14_smart_memory/ds_smart_pointers.h @@ -0,0 +1,77 @@ +#include + +// ============================================================================== +// basic auto pointer implementation +// see also http://ootips.org/yonat/4dev/smart-pointers.html + +template +class dsAutoPtr { +public: + explicit dsAutoPtr(T* p = NULL) : ptr(p) {} /* prevents cast/conversion */ + ~dsAutoPtr() { delete ptr; } + T& operator*() { return *ptr; } + T* operator->() { return ptr; } +private: + T* ptr; +}; + + +// ============================================================================== +// basic reference counting pointer borrowed from: +// http://www.codeproject.com/Articles/15351/Implementing-a-simple-smart-pointer-in-c + +class ReferenceCount { +public: + ReferenceCount() { count = 0; } + void addReference() { count++; } + int releaseReference() { return --count; } +private: + int count; +}; + + +template +class dsSharedPtr { +public: + dsSharedPtr(T* pValue = NULL) : pData(pValue) { + // Create a new reference counter & increment the count + reference = new ReferenceCount(); + reference->addReference(); + } + dsSharedPtr(const dsSharedPtr& sp) : pData(sp.pData), reference(sp.reference) { + // use the same reference counter, increment the count + reference->addReference(); + } + dsSharedPtr& operator= (const dsSharedPtr& sp) { + if (this != &sp) { + // Decrement the old reference count + // if reference become zero delete the old data + if(reference->releaseReference() == 0) { + delete pData; + delete reference; + } + // Copy the data and reference pointer + // and increment the reference count + pData = sp.pData; + reference = sp.reference; + reference->addReference(); + } + return *this; + } + // destructor + ~dsSharedPtr() { + if (reference->releaseReference() == 0) { + delete pData; + delete reference; + } + } + bool operator== (const dsSharedPtr& sp) { return pData == sp.pData; } + T& operator* () { return *pData; } + T* operator-> () { return pData; } +private: + // REPRESENTATION + T* pData; + ReferenceCount* reference; +}; + +// ============================================================================== diff --git a/labs/14_smart_memory/main_smart_pointers.cpp b/labs/14_smart_memory/main_smart_pointers.cpp new file mode 100644 index 0000000..709789d --- /dev/null +++ b/labs/14_smart_memory/main_smart_pointers.cpp @@ -0,0 +1,208 @@ +#include +#include +#include + +// simple homemade smart pointers +#include "ds_smart_pointers.h" + + +// ==================================================== +// BALLOON: a toy class with dynamically allocated memory +// ==================================================== + +#define MAX_NUM_ROPES 10 + +class Balloon { +public: + // CONSTRUCTOR & DESTRUCTOR + Balloon(const std::string& name_) : name(name_) { + std::cout << "Balloon constructor " << name << std::endl; + num_ropes = 0; + ropes = new Balloon*[MAX_NUM_ROPES]; + } + ~Balloon() { + std::cout << "Balloon destructor " << name << std::endl; + // don't try to destroy attached balloons, just delete array + delete [] ropes; + } + + // ACCESSORS + const std::string& getName() const { return name; } + int numRopes() const { return num_ropes; } + const Balloon* getRope(int i) const { return ropes[i]; } + + // print the balloons we are attached to + void print() { + std::cout << "Balloon " << name << " is connected to: "; + for (int i = 0; i < num_ropes; i++) { + std::cout << ropes[i]->name << " "; + } + if (num_ropes == 0) std::cout << "nothing"; + std::cout << std::endl; + } + // add a rope connecting this balloon to another + void addRope(Balloon* b) { + assert (num_ropes < MAX_NUM_ROPES); + ropes[num_ropes] = b; + num_ropes++; + } + // detach a rope connecting this balloon to another + void removeRope(Balloon* b) { + for (int i = 0; i < MAX_NUM_ROPES; i++) { + if (ropes[i] == b) { ropes[i] = ropes[num_ropes-1]; num_ropes--; break; } + } + } + +private: + std::string name; + int num_ropes; + // a dynamically allocated C-style array of ropes connecting to other Balloons + Balloon** ropes; +}; + + +// ==================================================== +// ==================================================== + +int main() { + + std::cout << "start of main" << std::endl; + + // ==================================================== + // SINGLE OWNER SMART POINTERS + // ==================================================== + + // first, without smart pointers! + Balloon* alice(new Balloon("Hello Kitty")); + + // now, with our homemade single owner smart pointer + dsAutoPtr bob(new Balloon("Spiderman")); + + // both alice & bob work like regular pointers... + alice->print(); + bob->print(); + + + + // + // CHECKPOINT 2A: INSERT NECESSARY EXPLICIT DEALLOCATION + // + + + + // ==================================================== + // SIMPLE SHARED POINTERS + // ==================================================== + + // first, without smart pointers + Balloon* cathy(new Balloon("Buzz Lightyear")); + Balloon* daniel(cathy); + Balloon* elaine(new Balloon("Pokemon")); + Balloon* fred(elaine); + daniel = fred; + fred = NULL; + elaine = cathy; + cathy = NULL; + + + + // + // CHECKPOINT 2B: INSERT NECESSARY EXPLICIT DEALLOCATION + // + + + + daniel = NULL; + elaine = NULL; + + + // now, with our homemade shared pointer + dsSharedPtr cathy2(new Balloon("Buzz Lightyear2")); + dsSharedPtr daniel2(cathy2); + dsSharedPtr elaine2(new Balloon("Pokemon2")); + dsSharedPtr fred2(elaine2); + daniel2 = fred2; + fred2 = NULL; + elaine2 = cathy2; + cathy2 = NULL; + // NOTE: no explicit destruction required! + daniel2 = NULL; + elaine2 = NULL; + + + // ==================================================== + // SHARED POINTERS WITH INTERCONNECTED STRUCTURES + // ==================================================== + + Balloon* georgette(new Balloon("Mr Potato Head")); + Balloon* henry(new Balloon("Snoopy")); + + georgette->addRope(henry); + henry = new Balloon("Tigger"); + georgette->addRope(henry); + georgette->print(); + henry->print(); + + Balloon* isabelle(new Balloon("Shrek")); + henry->addRope(isabelle); + isabelle = new Balloon("Barney the Purple Dinosaur"); + georgette->addRope(isabelle); + + henry->print(); + georgette->print(); + isabelle->print(); + + + // + // CHECKPOINT 2C: REWRITE THE ABOVE EXAMPLE TO USE SHARED POINTERS + // + + + + // ==================================================== + // CYCLIC STRUCTURES + // ==================================================== + + + // FOR CHECKPOINT 3 + + + /* + Balloon* jacob(new Balloon("Dora the Explorer")); + Balloon* katherine(new Balloon("Kung Fu Panda")); + Balloon* larry(new Balloon("Scooby Doo")); + Balloon* miranda(new Balloon("SpongeBob SquarePants")); + Balloon* nicole(new Balloon("Papa Smurf")); + + jacob->addRope(katherine); + katherine->addRope(larry); + larry->addRope(jacob); + miranda->addRope(jacob); + nicole->addRope(miranda); + larry->addRope(nicole); + + katherine = NULL; + larry = NULL; + miranda = NULL; + nicole = NULL; + + // jacob points to a cyclic structure! + + // to cleanup this structure: + deleteAll(jacob); + + jacob = NULL; + */ + + + + std::cout << "end of main" << std::endl; + return 0; + + // + // NOTE: when smart pointers go out of scope, the destructors for + // those objects will be called automatically + // +} + +// ==================================================== diff --git a/labs/14_smart_memory/main_stop_and_copy.cpp b/labs/14_smart_memory/main_stop_and_copy.cpp new file mode 100644 index 0000000..80e62e5 --- /dev/null +++ b/labs/14_smart_memory/main_stop_and_copy.cpp @@ -0,0 +1,58 @@ +#include +#include +#include "stop_and_copy.h" + + +int main() { + + StopAndCopy m; + std::cout << "TESTING StopAndCopy" << std::endl; + std::cout << std::endl << "empty memory:" << std::endl << m; + + // create an interesting data structure + m.root = m.my_new('a',MY_NULL,MY_NULL); + m.my_new('b',MY_NULL,m.root); + m.root = m.my_new('c',m.root,MY_NULL); + m[m.root].right = m.my_new('d',m[m.root].left,MY_NULL); + + std::cout << std::endl << "4 cells allocated:" << std::endl << m; + + m.root = m.my_new('e',MY_NULL,m.root); + m[m.root].right = m.my_new('f',m[m.root].right,MY_NULL); + m[m[m.root].right].right = m.my_new('g',m[m.root].right,MY_NULL); + m.root = m.my_new('h',m.root,MY_NULL); + m.root = m[m[m.root].left].right; + + std::cout << std::endl << "8 cells allocated:" << std::endl << m; + + // force garbage collection + m.collect_garbage(); + std::cout << std::endl << "after forced garbage collection:" << std::endl << m; + + // allocate more cells to force garbage collection + m[m.root].left = m.my_new('i',m.root,MY_NULL); + m.root = m.my_new('j',m.root,MY_NULL); + m.root = m.my_new('k',m.root,MY_NULL); + + std::cout << std::endl << "after adding 3 more cells:" << std::endl << m; + + + // Walk through the Stop And Copy garbage collection algorithm on + // the memory at this point in the program. Draw a pencil & paper + // diagram to show your work. + + + // UNCOMMENT THESE LINES AFTER YOU FINISH CHECKPOINT 1 (to check your work) + /* + m.root = m.my_new('l',m.root,MY_NULL); + std::cout << std::endl << "adding another cell triggers automatic garbage collection:" << std::endl << m; + + // "forget" the root pointer + m.root = MY_NULL; + m.collect_garbage(); + std::cout << std::endl << "root set to null & forced garbage collection:" << std::endl << m; + */ + +} + +// ============================================================================== diff --git a/labs/14_smart_memory/stop_and_copy.cpp b/labs/14_smart_memory/stop_and_copy.cpp new file mode 100644 index 0000000..5c4b90c --- /dev/null +++ b/labs/14_smart_memory/stop_and_copy.cpp @@ -0,0 +1,96 @@ +#include "stop_and_copy.h" + + +// Return the node corresponding to a particular address +Node& StopAndCopy::operator[](Address addr) { + if (addr == MY_NULL) { + std::cerr << "ERROR: NULL POINTER EXCEPTION!" << std::endl; exit(1); } + if (addr < OFFSET || addr >= OFFSET+CAPACITY) { + std::cerr << "ERROR: SEGMENTATION FAULT!" << std::endl; exit(1); } + return memory[addr-OFFSET]; +} + + +Address StopAndCopy::my_new(char v, Address l, Address r) { + // if we are out of memory, collect garbage + if (next == partition_offset+CAPACITY/2) { + collect_garbage(); + // update the addresses (since memory has been shuffled!) + if (l != MY_NULL) l = memory[l-OFFSET].left; + if (r != MY_NULL) r = memory[r-OFFSET].left; + } + // if we are still out of memory, we can't continue + if (next == partition_offset+CAPACITY/2) { + std::cerr << "ERROR: OUT OF MEMORY!" << std::endl; exit(1); } + // assign the next available node + memory[next].value = v; + memory[next].left = l; + memory[next].right = r; + return OFFSET + next++; +} + + +// Print function for debugging +std::ostream& operator<<(std::ostream &ostr, StopAndCopy &m) { + ostr << "root-> " << m.root << std::endl; + for (int i = 0; i < CAPACITY; i++) { + ostr.width(4); ostr << i+OFFSET << " "; } + ostr << std::endl; + for (int i = 0; i < CAPACITY; i++) { + ostr << " "; ostr.width(1); ostr << m.memory[i].value << " "; } + ostr << std::endl; + for (int i = 0; i < CAPACITY; i++) { + ostr.width(4); ostr << m.memory[i].left << " "; } + ostr << std::endl; + for (int i = 0; i < CAPACITY; i++) { + ostr.width(4); ostr << m.memory[i].right << " "; } + ostr << std::endl; + // print "FREE" or "used" for each node in the current partition + for (int i = 0; i < CAPACITY; i++) { + if (i >= m.next && i < m.partition_offset+CAPACITY/2) + ostr << "FREE "; + else if (i >= m.partition_offset && i < m.partition_offset+CAPACITY/2) + ostr << "used "; + else // print nothing for the other half of memory + ostr << " "; } + ostr << std::endl; + return ostr; +} + + +void StopAndCopy::collect_garbage() { + // switch to the other partition + partition_offset = (partition_offset == 0) ? CAPACITY/2 : 0; + // scan & next start at the beginning of the new partition + Address scan; + next = scan = partition_offset; + // copy the root + copy_help(root); + // scan through the newly copied nodes + while (scan != next) { + // copy the left & right pointers + copy_help(memory[scan].left); + copy_help(memory[scan].right); + scan++; + } +} + + +void StopAndCopy::copy_help(Address &old) { + // do nothing for NULL Address + if (old == MY_NULL) return; + // look for a valid forwarding address to the new partition + int forward = memory[old-OFFSET].left; + if (forward-OFFSET >= partition_offset && + forward-OFFSET < partition_offset+CAPACITY/2) { + // if already copied, change pointer to new address + old = forward; + return; + } + // otherwise copy it to a free slot and leave a forwarding address + memory[next] = memory[old-OFFSET]; + memory[old-OFFSET].left = next+OFFSET; + old = next+OFFSET; + next++; +} + diff --git a/labs/14_smart_memory/stop_and_copy.h b/labs/14_smart_memory/stop_and_copy.h new file mode 100644 index 0000000..4523bb2 --- /dev/null +++ b/labs/14_smart_memory/stop_and_copy.h @@ -0,0 +1,53 @@ +#include + +// size of memory available for this process +#define CAPACITY 16 +// first valid address for this process +#define OFFSET 100 +#define MY_NULL 0 + + +typedef int Address; + + +// A helper class for the StopAndCopy memory system +class Node { +public: + Node() { value='?'; left=-1; right=-1; } // initialized with "garbage" values + char value; + Address left; + Address right; +}; + + +// A simple implementation of the basic StopAndCopy garbage collector +class StopAndCopy { +public: + StopAndCopy() { + root = MY_NULL; + partition_offset = 0; + next = 0; + } + // Return the node corresponding to a particular address + Node& operator[](Address addr); + // allocate a new node + Address my_new(char v, Address l, Address r); + // a print function for debugging + friend std::ostream& operator<<(std::ostream &ostr, StopAndCopy &m); + // force automatic memory management + void collect_garbage(); + // REPRESENTATION +public: + // the user must set this value such that all useful memory is + // reachable starting from root (NOTE: publicly accessible) + Address root; +private: + // total machine memory + Node memory[CAPACITY]; + // which half of the memory is active + int partition_offset; + // next available node + int next; + // a private helper function + void copy_help(Address &old_address); +};