Merge branch 'master' of https://github.com/jidongxiao/CSCI1200-DataStructures
This commit is contained in:
68
labs/13_smart_memory/README.md
Normal file
68
labs/13_smart_memory/README.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Lab 13 — 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.
|
||||||
77
labs/13_smart_memory/ds_smart_pointers.h
Normal file
77
labs/13_smart_memory/ds_smart_pointers.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
// ==============================================================================
|
||||||
|
// basic auto pointer implementation
|
||||||
|
// see also http://ootips.org/yonat/4dev/smart-pointers.html
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
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 T>
|
||||||
|
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<T>& sp) : pData(sp.pData), reference(sp.reference) {
|
||||||
|
// use the same reference counter, increment the count
|
||||||
|
reference->addReference();
|
||||||
|
}
|
||||||
|
dsSharedPtr<T>& operator= (const dsSharedPtr<T>& 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<T>& sp) { return pData == sp.pData; }
|
||||||
|
T& operator* () { return *pData; }
|
||||||
|
T* operator-> () { return pData; }
|
||||||
|
private:
|
||||||
|
// REPRESENTATION
|
||||||
|
T* pData;
|
||||||
|
ReferenceCount* reference;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================================================================
|
||||||
208
labs/13_smart_memory/main_smart_pointers.cpp
Normal file
208
labs/13_smart_memory/main_smart_pointers.cpp
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <cassert>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// 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<Balloon> 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<Balloon> cathy2(new Balloon("Buzz Lightyear2"));
|
||||||
|
dsSharedPtr<Balloon> daniel2(cathy2);
|
||||||
|
dsSharedPtr<Balloon> elaine2(new Balloon("Pokemon2"));
|
||||||
|
dsSharedPtr<Balloon> 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
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
58
labs/13_smart_memory/main_stop_and_copy.cpp
Normal file
58
labs/13_smart_memory/main_stop_and_copy.cpp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <cassert>
|
||||||
|
#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;
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==============================================================================
|
||||||
96
labs/13_smart_memory/stop_and_copy.cpp
Normal file
96
labs/13_smart_memory/stop_and_copy.cpp
Normal file
@@ -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++;
|
||||||
|
}
|
||||||
|
|
||||||
53
labs/13_smart_memory/stop_and_copy.h
Normal file
53
labs/13_smart_memory/stop_and_copy.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
};
|
||||||
291
lectures/25_garbage_collection/README.md
Normal file
291
lectures/25_garbage_collection/README.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
# Lecture 25 --- Garbage Collection & Smart Pointers
|
||||||
|
|
||||||
|
## Today’s Lecture
|
||||||
|
|
||||||
|
- What is Garbage?
|
||||||
|
- 3 Garbage Collection Techniques
|
||||||
|
- Smart Pointers
|
||||||
|
|
||||||
|
## 25.1 What is Garbage?
|
||||||
|
|
||||||
|
- Not everything sitting in memory is useful. Garbage is anything that cannot have any influence on the future
|
||||||
|
computation.
|
||||||
|
- With C++, the programmer is expected to perform explicit memory management. You must use delete when
|
||||||
|
you are done with dynamically allocated memory (which was created with new).
|
||||||
|
- In Java, and other languages with “garbage collection”, you are not required to explicitly de-allocate the
|
||||||
|
memory. The system automatically determines what is garbage and returns it to the available pool of memory.
|
||||||
|
Certainly this makes it easier to learn to program in these languages, but automatic memory management does
|
||||||
|
have performance and memory usage disadvantages.
|
||||||
|
- Today we’ll overview 3 basic techniques for automatic memory management.
|
||||||
|
|
||||||
|
## 25.2 The Node class
|
||||||
|
|
||||||
|
- For our discussion today, we’ll assume that all program data is stored in dynamically-allocated instances of the
|
||||||
|
following simple class. This class can be used to build linked lists, trees, and graphs with cycles:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Node {
|
||||||
|
public:
|
||||||
|
Node(char v, Node* l, Node* r) :
|
||||||
|
value(v), left(l), right(r) {}
|
||||||
|
char value;
|
||||||
|
Node* left;
|
||||||
|
Node* right;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 25.3 Garbage Collection Technique #1: Reference Counting
|
||||||
|
|
||||||
|
1. Attach a counter to each Node in memory.
|
||||||
|
2. When a new pointer is connected to that Node, increment the counter.
|
||||||
|
3. When a pointer is removed, decrement the counter.
|
||||||
|
4. Any Node with counter == 0 is garbage and is available for reuse.
|
||||||
|
|
||||||
|
## 25.4 Reference Counting Exercise
|
||||||
|
|
||||||
|
- Draw a “box and pointer” diagram for the following example, keeping a “reference counter” with each Node.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node *a = new Node('a', NULL, NULL);
|
||||||
|
Node *b = new Node('b', NULL, NULL);
|
||||||
|
Node *c = new Node('c', a, b);
|
||||||
|
a = NULL;
|
||||||
|
b = NULL;
|
||||||
|
c->left = c;
|
||||||
|
c = NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
- Is there any garbage?
|
||||||
|
|
||||||
|
## 25.5 Memory Model Exercise
|
||||||
|
|
||||||
|
- In memory, we pack the Node instances into a big array. In the toy example below, we have only enough room in memory to store 8 Nodes, which are addressed 100 -> 107. 0 is a NULL address.
|
||||||
|
- For simplicity, we’ll assume that the program uses only one variable, root, through which it accesses all of the
|
||||||
|
data. Draw the box-and-pointer diagram for the data accessible from root = 105.
|
||||||
|
|
||||||
|
```console
|
||||||
|
address 100 101 102 103 104 105 106 107
|
||||||
|
value a b c d e f g h
|
||||||
|
left 0 0 100 100 0 102 105 104
|
||||||
|
right 0 100 103 0 105 106 0 0
|
||||||
|
root: 105
|
||||||
|
```
|
||||||
|
|
||||||
|
- What memory is garbage?
|
||||||
|
|
||||||
|
## 25.6 Garbage Collection Technique #2: Stop and Copy
|
||||||
|
|
||||||
|
1. Split memory in half (working memory and copy memory).
|
||||||
|
2. When out of working memory, stop computation and begin garbage collection.
|
||||||
|
(a) Place scan and free pointers at the start of the copy memory.
|
||||||
|
(b) Copy the root to copy memory, incrementing free. Whenever a node is copied from working memory, leave a forwarding address to its new location in copy memory in the left address slot of its old location.
|
||||||
|
(c) Starting at the scan pointer, process the left and right pointers of each node. Look for their locations in working memory. If the node has already been copied (i.e., it has a forwarding address), update the reference. Otherwise, copy the location (as before) and update the reference.
|
||||||
|
(d) Repeat until scan == free.
|
||||||
|
(e) Swap the roles of the working and copy memory.
|
||||||
|
|
||||||
|
## 25.7 Stop and Copy Exercise
|
||||||
|
|
||||||
|
Perform stop-and-copy on the following with root = 105:
|
||||||
|
|
||||||
|
```console
|
||||||
|
WORKING MEMORY
|
||||||
|
address 100 101 102 103 104 105 106 107
|
||||||
|
value a b c d e f g h
|
||||||
|
left 0 0 100 100 0 102 105 104
|
||||||
|
right 0 100 103 0 105 106 0 0
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
COPY MEMORY
|
||||||
|
address 108 109 110 111 112 113 114 115
|
||||||
|
value
|
||||||
|
left
|
||||||
|
right
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
root: 105
|
||||||
|
scan:
|
||||||
|
free:
|
||||||
|
```
|
||||||
|
|
||||||
|
## 25.8 Garbage Collection Technique #3: Mark-Sweep
|
||||||
|
|
||||||
|
1. Add a mark bit to each location in memory.
|
||||||
|
2. Keep a free pointer to the head of the free list.
|
||||||
|
3. When memory runs out, stop computation, clear the mark bits and begin garbage collection.
|
||||||
|
4. Mark
|
||||||
|
(a) Start at the root and follow the accessible structure (keeping a stack of where you still need to go).
|
||||||
|
(b) Mark every node you visit.
|
||||||
|
(c) Stop when you see a marked node, so you don’t go into a cycle.
|
||||||
|
5. Sweep
|
||||||
|
(a) Start at the end of memory, and build a new free list.
|
||||||
|
(b) If a node is unmarked, then it’s garbage, so hook it into the free list by chaining the left pointers.
|
||||||
|
|
||||||
|
## 25.9 Mark-Sweep Exercise
|
||||||
|
|
||||||
|
Let’s perform Mark-Sweep on the following with root = 105:
|
||||||
|
|
||||||
|
```console
|
||||||
|
address 100 101 102 103 104 105 106 107
|
||||||
|
value a b c d e f g h
|
||||||
|
left 0 0 100 100 0 102 105 104
|
||||||
|
right 0 100 103 0 105 106 0 0
|
||||||
|
marks
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
root: 105
|
||||||
|
free:
|
||||||
|
stack:
|
||||||
|
```
|
||||||
|
|
||||||
|
## 25.10 Garbage Collection Comparison
|
||||||
|
|
||||||
|
- Reference Counting:
|
||||||
|
|
||||||
|
- \+ fast and incremental
|
||||||
|
- – can’t handle cyclical data structures!
|
||||||
|
- ? requires ∼33% extra memory (1 integer per node)
|
||||||
|
|
||||||
|
- Stop & Copy:
|
||||||
|
|
||||||
|
- \- requires a long pause in program execution
|
||||||
|
- \+ can handle cyclical data structures!
|
||||||
|
- – requires 100% extra memory (you can only use half the memory)
|
||||||
|
- \+ runs fast if most of the memory is garbage (it only touches the nodes reachable from the root)
|
||||||
|
- \+ data is clustered together and memory is “de-fragmented”
|
||||||
|
|
||||||
|
- Mark-Sweep:
|
||||||
|
|
||||||
|
- \- requires a long pause in program execution
|
||||||
|
- \+ can handle cyclical data structures!
|
||||||
|
- \+ requires ∼1% extra memory (just one bit per node)
|
||||||
|
- \- runs the same speed regardless of how much of memory is garbage.
|
||||||
|
It must touch all nodes in the mark phase, and must link together all garbage nodes into a free list.
|
||||||
|
|
||||||
|
## 25.11 Practical Garbage Collection Methodology in C++: Smart Pointers
|
||||||
|
|
||||||
|
Garbage collection looks like an attractive option both when we are quickly drafting a prototype system and
|
||||||
|
also when we are developing big complex programs that process and rearrange lots of data.
|
||||||
|
Unfortunately, general-purpose, invisible garbage collection isn’t something we can just tack onto C++, an
|
||||||
|
enormous beast of a programming language (but that doesn’t stop people from trying!). So is there anything
|
||||||
|
we can do? Yes, we can use Smart Pointers to gain some of the features of garbage collection.
|
||||||
|
Some examples below are modified from these nice online references:
|
||||||
|
|
||||||
|
[http://ootips.org/yonat/4dev/smart-pointers.html](http://ootips.org/yonat/4dev/smart-pointers.html)
|
||||||
|
|
||||||
|
[http://www.codeproject.com/KB/stl/boostsmartptr.aspx](http://www.codeproject.com/KB/stl/boostsmartptr.aspx)
|
||||||
|
|
||||||
|
[http://en.wikipedia.org/wiki/Smart_pointer](http://en.wikipedia.org/wiki/Smart_pointer)
|
||||||
|
|
||||||
|
[http://www.boost.org/doc/libs/1_48_0/libs/smart_ptr/smart_ptr.htm](http://www.boost.org/doc/libs/1_48_0/libs/smart_ptr/smart_ptr.htm)
|
||||||
|
|
||||||
|
[http://www.acodersjourney.com/2016/05/top-10-dumb-mistakes-avoid-c-11-smart-pointers/](http://www.acodersjourney.com/2016/05/top-10-dumb-mistakes-avoid-c-11-smart-pointers/)
|
||||||
|
|
||||||
|
## 25.12 What’s a Smart Pointer?
|
||||||
|
|
||||||
|
- The goal is to create a widget that works just like a regular pointer most of the time, except at the beginning
|
||||||
|
and end of its lifetime. The syntax of how we construct smart pointers is a bit different and we don’t need to
|
||||||
|
obsess about how & when it will get deleted (it happens automatically).
|
||||||
|
- Here’s one flavor of a smart pointer (simplified from STL):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <class T>
|
||||||
|
class auto_ptr {
|
||||||
|
public:
|
||||||
|
explicit auto_ptr(T* p = NULL) : ptr(p) {} /* prevents cast/conversion */
|
||||||
|
~auto_ptr() { delete ptr; }
|
||||||
|
T& operator*() { return *ptr; }
|
||||||
|
T* operator->() { return ptr; } /* fakes being a pointer */
|
||||||
|
private:
|
||||||
|
T* ptr;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- And let’s start with some example code without smart pointers:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void foo() {
|
||||||
|
Polygon* p(new Polygon(/* stuff */));
|
||||||
|
p->DoSomething();
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Here’s how we can re-write the same example with our auto_ptr:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void foo() {
|
||||||
|
auto_ptr<Polygon> p(new Polygon(/* stuff */);
|
||||||
|
p->DoSomething();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- We don’t have to call delete! There’s no memory leak or memory error in this code. Awesome!
|
||||||
|
|
||||||
|
## 25.13 So, What are the Advantages of Smart Pointers?
|
||||||
|
|
||||||
|
- With practice, smart pointers can result in code that is more concise and elegant with fewer errors. Why? ...
|
||||||
|
- With thoughtful use, smart pointers make it easier to follow the principles of RAII and make code exception safe. In the auto_ptr example above, if DoSomething throws an exception, the memory for object p will be properly deallocated when we leave the scope of the foo function! This is not the case with the original version.
|
||||||
|
- The STL shared_ptr flavor implements reference counting garbage collection. Awesome<sup>2</sup>!
|
||||||
|
- They play nice with STL containers. Say you make an std::vector (or std::list, or std::map, etc.) of regular pointers to Polygon objects, Polygon* (especially handy if this is a polymorphic collection of objects!).
|
||||||
|
You allocate them all with new, and when you are all finished you must remember to explicitly deallocate each
|
||||||
|
of the objects.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Polygon { /*...*/ };
|
||||||
|
class Triangle : public Polygon { /*...*/ };
|
||||||
|
class Quad : public Polygon { /*...*/ };
|
||||||
|
std::vector<Polygon*> polys;
|
||||||
|
polys.push_back(new Triangle(/*...*/));
|
||||||
|
polys.push_back(new Quad(/*...*/));
|
||||||
|
for (unsigned int i = 0; i < polys.size(); i++) {
|
||||||
|
delete polys[i];
|
||||||
|
}
|
||||||
|
polys.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
In contrast, with smart pointers they will be deallocated automagically!
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::vector<shared_ptr<Polygon> > polys;
|
||||||
|
polys.push_back(shared_ptr<Polygon>(new Triangle(/*...*/)));
|
||||||
|
polys.push_back(shared_ptr<Polygon>(new Quad(/*...*/)));
|
||||||
|
polys.clear(); // cleanup is automatic!
|
||||||
|
```
|
||||||
|
|
||||||
|
## 25.14 Why are Smart Pointers Tricky?
|
||||||
|
|
||||||
|
- Smart pointers do not alleviate the need to master pointers, basic memory allocation & deallocation, copy constructors, destructors, assignment operators, and reference variables.
|
||||||
|
- You can still make mistakes in your smart pointer code that yield the same types of memory corruption,
|
||||||
|
segmentation faults, and memory leaks as regular pointers.
|
||||||
|
- There are several different flavors of smart pointers to choose from (developed for different uses, for common
|
||||||
|
design patterns). You need to understand your application and the different pitfalls when you select the
|
||||||
|
appropriate implementation.
|
||||||
|
|
||||||
|
## 25.15 What are the Different Types of Smart Pointers?
|
||||||
|
|
||||||
|
Like other parts of the C++ standard, these tools are still evolving. The different choices reflect different ownership
|
||||||
|
semantics and different design patterns. There are some smart pointers in STL, and also some in Boost (a C++
|
||||||
|
library that further extends the current STL). A quick overview:
|
||||||
|
- auto_ptr
|
||||||
|
When “copied” (copy constructor), the new object takes ownership and the old object is now empty. Deprecated
|
||||||
|
in new C++ standard.
|
||||||
|
- unique_ptr
|
||||||
|
Cannot be copied (copy constructor not public). Can only be “moved” to transfer ownership. Explicit ownership
|
||||||
|
transfer. Intended to replace auto_ptr. std::unique ptr has memory overhead only if you provide it with some
|
||||||
|
non-trivial deleter. It has time overhead only during constructor (if it has to copy the provided deleter) and
|
||||||
|
during destructor (to destroy the owned object).
|
||||||
|
- scoped_ptr (Boost)
|
||||||
|
“Remembers” to delete things when they go out of scope. Alternate to auto_ptr. Cannot be copied.
|
||||||
|
- shared_ptr
|
||||||
|
Reference counted ownership of pointer. Unfortunately, circular references are still a problem. Different subflavors based on where the counter is stored in memory relative to the object, e.g., intrusive_ptr, which
|
||||||
|
is more memory efficient. std::unique ptr has memory overhead only if you provide it with some non-trivial
|
||||||
|
deleter. It has time overhead in constructor (to create the reference counter), in destructor (to decrement
|
||||||
|
the reference counter and possibly destroy the object) and in assignment operator (to increment the reference
|
||||||
|
counter).
|
||||||
|
- weak_ptr
|
||||||
|
Use with shared_ptr. Memory is destroyed when no more shared_ptrs are pointing to object. So each time
|
||||||
|
a weak_ptr is used you should first “lock” the data by creating a shared_ptr.
|
||||||
|
- scoped_array and shared_array (Boost)
|
||||||
|
|
||||||
Reference in New Issue
Block a user