Want to view this project? In-Memory Record Database
Destroying the Record Table
Back in the last article, we saw a function in mainMenu called destroyRecordTable. This function systematically frees up the memory of all struct RecordNodes and all struct Records until our struct RecordTable is empty. It would be as if we called initializeRecordTable and didn't pre-create our records.
Now technically, we don't need this function at all since once we close the console window, it frees up the memory anyways. But I personally don't like to instill bad habits like this. I can't tell you how many times I've seen some truly awful code (I program in C# for my day job) only for it to be justified because "the compiler will take care of it for me". There's going to come a time when we have to do this ourselves and it's better we start getting used to it now.
So how would we actually go about destroying the table? Let's remember how we set this program up to begin with. We have:
- 4 columns (ID, First Name, Last Name, Age)
- Each column has its own AVL Tree
- A single instance of a
struct Record- Each AVL Tree node points to this single record. It does not copy it.
We've already see scenarios where we delete record nodes, but not the record itself, back in part 7 when we edited records and that'll help us out here too. We should first free the memory of the nodes in all trees first before freeing the memory of the record itself.
Okay, that makes sense, but where do we even begin? What tree do we start with? Well, it doesn't really matter, but I'm starting by removing the nodes from all trees except the ID tree. You can do this is any order you want as long as you first remove the nodes from all other trees BEFORE touching the last tree.
void destroyRecordTable(struct RecordTable *table)
{
destroyTreeNodesOnly(&table->ageTree);
destroyTreeNodesOnly(&table->lastNameTree);
destroyTreeNodesOnly(&table->firstNameTree);
destroyTreeNodesAndRecords(&table->idTree);
}
As you can see, we're removing the nodes only from the age tree, last name tree, and first name tree. We'll actually remove the node and record itself at the same time when we reach the last tree, but let's not get too ahead of ourselves. Let's take a look at what destroyTreeNodesOnly is doing.
This function calls destroyNodesOnly which recursively frees up the memory allocated for the struct RecordNode in the tree.
void destroyTreeNodesOnly(struct RecordAVLTree *tree)
{
destroyNodesOnly(tree->root);
tree->root = NULL;
}
static void destroyNodesOnly(struct RecordNode *root)
{
if (root == NULL)
{
return;
}
destroyNodesOnly(root->left);
destroyNodesOnly(root->right);
free(root);
}
destroyTreeNodesAndRecords does the exact same thing, except now we're freeing the struct Record before freeing the struct RecordNode.
void destroyTreeNodesAndRecords(struct RecordAVLTree *tree)
{
destroyNodesAndRecords(tree->root);
tree->root = NULL;
}
static void destroyNodesAndRecords(struct RecordNode *root)
{
if (root == NULL)
{
return;
}
destroyNodesAndRecords(root->left);
destroyNodesAndRecords(root->right);
free(root->record);
free(root);
}
I can't stress enough how important the ordering is for this. Do not under any circumstance remove the
struct Recorduntil you've ensured that all of the otherstruct RecordNodes in all other trees have been removed. Only then can you go into your last tree (whatever tree that may be) and free thestruct Recordalong with itsstruct RecordNode. You can easily get some dangling pointers this way and cause a whole mess of problems for yourself assuming you're needing to do something like this where closing the program doesn't automatically free up all the memory for you.
This destroyRecordTable function should already exist at the bottom of mainMenu, so with that done, we've officially completed our in-memory record database system! Ship it!
Closing Remarks
This was a great exercise for me to really understand not just AVL trees and how to implement them practically, but also pointers and memory management. I had taken a C class back when I went to TSTC Waco and I learned a lot of the basics of programming as anyone does, but it never went in depth on pointers and memory management. When I eventually went to WGU and took my first data structures and algorithms classes, that too left a lot to be desired. I'm very thankful that I decided to go this route to really teach myself how all of this clicks together. It's helped me appreciate and understand database systems a lot more and I'm hoping that I can revisit this concept again one day using secondary storage.
If that time ever comes, I'll be sure to do a write up on it. If you've made it this far, thanks for sticking around and reading this! I hope you've learned a few things yourself along the way. If you have any questions or comments related to this article series, feel free to reach out. My email is listed on the home page of this website.