adding 14

This commit is contained in:
Jidong Xiao
2024-04-16 17:01:34 -04:00
parent 8f5f7afa7a
commit 7a96b40c70
6 changed files with 560 additions and 0 deletions

View File

@@ -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 Macys 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, lets 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, lets 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.

View 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;
};
// ==============================================================================

View 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
//
}
// ====================================================

View 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;
*/
}
// ==============================================================================

View 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++;
}

View 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);
};