Lecture 7: Reference Counting and Garbage Collection

Lecture 7: Reference Counting and Garbage Collection

Programming in C and C++ Lecture 7: Reference Counting and Garbage Collection Neel Krishnaswami and Alan Mycroft 1 / 30 The C API for Dynamic Memory Allocation • In the previous lecture, we saw how to use arenas and ad-hoc graph traversals to manage memory when pointer graphs contain aliasing or cycles • These are not the only idioms for memory management in C! • Two more common patterns are reference counting and type-specific garbage collectors. 2 / 30 A Tree Data Type 1 struct node { 2 int value; 3 struct node*left; 4 struct node*right; 5 }; 6 typedef struct node Tree; • This is still the tree type from Lab 4. • It has a value, a left subtree, and a right subtree • An empty tree is a NULL pointer. 3 / 30 Construct Nodes of a Tree 1 Tree*node(int value, Tree*left, Tree*right) { 2 Tree*t= malloc(sizeof(tree)); 3 t->value= value; 4 t->right= right; 5 t->left= left; 6 return t; 7 } 1. Allocate a pointer to a tree struct 2. Initialize the value field 3. Initialize the right field 4. Initialize the left field 5. Return the initialized pointer! 4 / 30 A Directed Acyclic Graph (DAG) 1 Tree*n= node(2, NULL, NULL); 2 Tree*n2= 3 node(1, n, n); // n repeated! 1. We allocate n on line 1 2. On line 2, we create n2 whose left and right fields are n. 3. Hence n2->left and n2->right are said to alias { they are two pointers aimed at the same block of memory. 5 / 30 The shape of the graph n2 left right • node1 has two pointers to node2 n • This is a directed acyclic graph, not a tree. • A recursive free of the tree right left n2 will try to free n twice. Null Null 6 / 30 The Idea of Reference Counting n2: k 1. The problem: freeing things left right with two pointers to them twice n: 2 2. Solution: stop doing that 3. Keep track of the number of pointers to an object left right 4. Only free when the count reaches zero Null Null 7 / 30 2. Eventually k becomes 0 3. It's time to delete n2 4. Decrement the reference count of each thing n2 points to 5. Then delete n2 6. Recursively delete n How Reference Counting Works n2: k 1. We start with k references to n2 left right n: 2 left right Null Null 8 / 30 3. It's time to delete n2 4. Decrement the reference count of each thing n2 points to 5. Then delete n2 6. Recursively delete n How Reference Counting Works n2: 0 1. We start with k references to n2 left right 2. Eventually k becomes 0 n: 2 left right Null Null 8 / 30 4. Decrement the reference count of each thing n2 points to 5. Then delete n2 6. Recursively delete n How Reference Counting Works n2: 0 1. We start with k references to n2 left right 2. Eventually k becomes 0 3. It's time to delete n2 n: 2 left right Null Null 8 / 30 5. Then delete n2 6. Recursively delete n How Reference Counting Works n2: 0 1. We start with k references to n2 left right 2. Eventually k becomes 0 3. It's time to delete n2 n: 2 4. Decrement the reference count of each thing n2 left right points to Null Null 8 / 30 5. Then delete n2 6. Recursively delete n How Reference Counting Works n2: 0 1. We start with k references to n2 left right 2. Eventually k becomes 0 3. It's time to delete n2 n: 1 4. Decrement the reference count of each thing n2 left right points to Null Null 8 / 30 6. Recursively delete n How Reference Counting Works n2: 0 1. We start with k references to n2 left right 2. Eventually k becomes 0 3. It's time to delete n2 n: 0 4. Decrement the reference count of each thing n2 left right points to 5. Then delete n2 Null Null 8 / 30 6. Recursively delete n How Reference Counting Works n2: 0 1. We start with k references to n2 left right 2. Eventually k becomes 0 3. It's time to delete n2 n: 0 4. Decrement the reference count of each thing n2 left right points to 5. Then delete n2 Null Null 8 / 30 How Reference Counting Works n2: 0 1. We start with k references to n2 left right 2. Eventually k becomes 0 3. It's time to delete n2 n: 0 4. Decrement the reference count of each thing n2 left right points to 5. Then delete n2 6. Recursively delete n Null Null 8 / 30 The Reference Counting API 1 struct node { 2 unsigned int rc; • We add a field rc to keep 3 int value; track of the references. 4 struct node*left; • We keep the same node 5 struct node*right; constructor interface. 6 }; 7 typedef struct node Node; • We add a procedure 8 inc_ref to increment the 9 const Node*empty= NULL; reference count of a node. 10 Node*node(int value, • We add a procedure 11 Node*left, 12 Node*right); dec_ref to decrement the 13 void inc_ref(Node*node); reference count of a node. 14 void dec_ref(Node*node); 9 / 30 Reference Counting Implementation: node() 1 Node*node(int value, 2 Node*left, • On line 4, we initialize the 3 Node*right) { rc field to 1. (Annoyingly, 4 Node*r= malloc(sizeof(Node)); this is a rather delicate 5 r->rc=1; point!) 6 r->value= value; 7 • On line 8-9, we set the left 8 r->left= left; field, and increment the 9 inc_ref(left); reference count of the 10 pointed-to node. 11 r->right= right; • On line 11-12, we do the 12 inc_ref(right); same to right 13 return r; 14 } 10 / 30 Reference Counting Implementation: inc ref() 1 void inc_ref(Node*node) { 2 if (node != NULL){ 3 node->rc +=1; 4 } 5 } • On line 3, we increment the rc field (if nonnull) • That's it! 11 / 30 Reference Counting Implementation: dec ref() • When we decrement a 1 void dec_ref(Node*node) { reference count, we check to 2 if (node != NULL){ see if we are the last 3 if (node->rc>1){ reference (line 3) 4 node->rc -=1; 5 } else{ • If not, we just decrement 6 dec_ref(node->left); the reference count (line 4) 7 dec_ref(node->right); • If so, then decrement the 8 free(node); reference counts of the 9 } children (lines 6-7) 10 } • Then free the current 11 } object. (line 8) 12 / 30 Example 1 • complete(n) builds a complete binary tree of 1 Node*complete(int n) { depth n 2 if (n ==0){ • Sharing makes memory 3 return empty; usage O(n) 4 } else{ • On line 5, makes a recursive 5 Node*sub= complete(n-1); 6 Node*result= call to build subtree. 7 node(n, sub, sub); • On line 6, builds the tree 8 dec_ref(sub); • On line 8, call 9 return result; dec_ref(sub) to drop the 10 } 11 } stack reference sub • On line 9, don't call dec_ref(result) 13 / 30 Example 1 { mistake 1 • If we forget to call dec_ref(sub), we get a 1 Node*complete(int n) { memory leak! 2 if (n ==0){ 3 return empty; • sub begins with a refcount 4 } else{ of 1 5 Node*sub= complete(n-1); • node(sub, sub) bumps it 6 Node*result= to 3 7 node(n, sub, sub); 8 // dec_ref(sub); • If we call 9 return result; dec_ref(complete(n)), 10 } the outer node will get freed 11 } • But the children will end up with an rc field of 1 14 / 30 Example 1 { mistake 2 • This still leaks memory! • complete(n-1) begins with 1 Node*complete(int n) { a refcount of 1 2 if (n ==0){ • The expression on lines 5-7 3 return empty; 4 } else{ bumps each subtree to a 5 return node(n, refcount of 2 6 complete(n-1), • If we call 7 complete(n-1)); free(complete(n)), the 8 } outer node will get freed 9 } • But the children will end up with an rc field of 1 15 / 30 Design Issues with Reference Counting APIs • The key problem: who is responsible for managing reference counts? • Two main options: sharing references vs transferring references • Both choices work, but must be made consistently • To make this work, API must be documented very carefully • Good example: Python C API • https://docs.python.org/3/c-api/intro.html# objects-types-and-reference-counts 16 / 30 Mitigations: Careful Use of Getters and Setters 1 Node*get_left(Node*node) { • The get_left() function 2 inc_ref(node->left); returns the left subtree, but 3 return(node->left); also increments the 4 } reference count 5 6 void set_left(Node*node, • The set_left() function 7 Node*newval) { updates the left subtree, 8 inc_ref(newval); incrementing the reference 9 dec_ref(node->left); count to the new value and 10 node->left= newval; decrementing the reference 11 } 17 / 30 Cycles: A Fundamental Limitation on Reference Counting 1 Node*foo() { 2 Node*n1= node(1, NULL, NULL); 3 Node*n2= node(2, NULL, NULL); 4 set_left(node1, node2); 5 set_left(node2, node1); 6 dec_ref(n2); 7 return node1; 8 } What does a call to foo() build? 18 / 30 A Cyclic Object Graph • n1->rc is 2, since n2 points to it • n2->rc is 1, since n1 points to it n1: 2 right • This is a cyclic graph • Even though there is only 1 external reference to n1, left left Null n1->rc is 2.

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    37 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us