Reference Counting in C for Your Sanity

Written by Meowing Cat on 6/5/2024, 3:22:00 AM

Learn how to implement reference counting in C to manage memory and avoid memory leaks and save your sanity.

Hewwooo, Cat is back with another tutorial! šŸ± Today, we will learn how to implement reference-counting in C and writing complicated apps/services in C just like with scripting comfortably.

Introduction

Cat implementing reference-counting.

C is my favorite language, it's fast, low-level, and gives you full control over your code and makes you feel like a real cat-hacker. šŸ™€

But, managing memory in C can be a real pain in the tail. šŸ˜æ Because, imagine you are writing a game server or an API service for something in C... a language that you can also write operating systems šŸ™€

You have to manage memory, and if you don't do it right, you will have memory leaks, and your app will crash, and you will be sad. šŸ˜æ

But, don't worry, Cat is here to help you! šŸˆ


Today, we will learn how to implement reference counting in C to manage memory and avoid memory leaks and save your sanity. With this technique, you can write complicated apps/services in C just like with the comfort of scripting.

Also before we start, Cat stronlgy recommend you to use his cute GDBFrontend for debugging your C/C++ apps. Itā€™s worldā€™s cutest debugger! šŸ¾

Also, the Valgrind my hero saves lives and helps you to find memory leaks in your C/C++ apps. You can check it out from Valgrindā€™s official website.

Catā€™s PokerUnicorn server is written in C and it implements an atomic reference counting mechanism for managing memory and avoiding memory leaks and race-conditions. You can read the docs, PokerUnicornā€™s Atomic Reference Counting.

What is Reference Counting?

Reference counting is a memory management technique that keeps track of the number of references to an object. When the reference count reaches zero, the object is deallocated.

Here is how it works:

  1. When you create an object, you init the reference count to 0.
  2. When you pass the object to another function or store it in a data structure, you increment the reference count.
  3. When you are done with the object, you decrement the reference count.
  4. When the reference count reaches zero, you deallocate the object.

This way, you can manage memory without worrying about memory leaks.

Implementing Reference Counting in C

Letā€™s implement reference counting in C with a simple example.

Simply, weā€™ll do these steps:

  1. Define a struct (and type for it) kinda a base reference-counted class.
  2. Implement functions to create, retain, and release the object.
  3. Implement a function to deallocate the object when the reference count reaches zero.

šŸ§‘šŸ»ā€šŸ¦± Butā€¦ Catā€¦ We donā€™t have classes in C! šŸ˜æ

šŸˆ Yes, I know, but we still have structs, and we can use them to implement reference counting. Just imagine they are classes. šŸ˜‡

Before We Start

For this tutorial, cat is gonna teach you things in a single C header file. You can copy this code and use it in your projects.

Just create a new file called ref.h and put all the code into it. Youā€™ll just need to include this file in your project to use reference counting.

Making a ref_counted_t C type!

First, letā€™s define a struct for our reference-counted object:

typedef struct ref_counted_t ref_counted_t;
typedef void (*ref_free_f_t)(void*);

struct ref_counted {
    int count;
    ref_free_f_t free_f;
};

Here, we have a struct ref_counted called ref_counted_t with three fields:

  1. ref_counted_t::count - The reference count of the object.
  2. ref_counted_t::free_f - A function pointer to deallocate the object.

For the ref_counted_t::free_f field, we are using a function pointer that takes a void* pointer as an argument and returns void. This function will be called to deallocate the object when the reference count reaches zero. We declared a type ref_free_f_t for this function pointer.

Implementing Methods (Actually Functions!)

Next, letā€™s implement functions to init, increase ref-count and decrease (or ā€œdecrease and free (deallocate)ā€) methods (yes, actually functions!).

Initialize the Reference Counted Object ref_counted_init(ref_counted, free_f)

void ref_counted_init(ref_counted_t* ref_counted, ref_free_f_t free_f) {
    ref_counted->count = 0;
    ref_counted->free_f = free_f;
}

This function initializes the reference count to zero and sets the deallocation function.

Increase the Reference Count ref_counted_use(ref_counted)

void ref_counted_use(ref_counted_t* ref_counted) {
    ref_counted->count++;
}

Simple, right?

Decrease the Reference Count ref_counted_leave(ref_counted)

Releasing one lock is a bit more complicated. We need to check if the reference count is zero and deallocate the object if it is.

void ref_counted_leave(void** obj_vp, ref_counted_t* ref_counted) {
    void* to_free = *obj_vp;
    
    ref_counted->count--;

    if (ref_counted->count == 0) {
        *obj_vp = NULL;
        ref_counted->free_f(to_free);
    }
}

But still simpleā€¦ Letā€™s keep going!

Oki but where is the full code?

Donā€™t worry cat has the full code for you! šŸ˜‡ But donā€™t forget to gimme sushi snacks! You can taka a look at my GitHub for the ways to donate.

The Single Header ref.h
#pragma once

typedef struct ref_counted_t ref_counted_t;
typedef void (*ref_free_f_t)(void*);

struct ref_counted_t {
    int count;
    ref_free_f_t free_f;
};

void ref_counted_init(ref_counted_t* ref_counted, ref_free_f_t free_f) {
    ref_counted->count = 0;
    ref_counted->free_f = free_f;
}

void ref_counted_use(ref_counted_t* ref_counted) {
    ref_counted->count++;
}

void ref_counted_leave(void** obj_vp, ref_counted_t* ref_counted) {
    void* to_free = *obj_vp;
    
    ref_counted->count--;

    if (ref_counted->count == 0) {
        *obj_vp = NULL;
        ref_counted->free_f(to_free);
    }
}

Using Reference Counting

Now, letā€™s see how to use reference counting in your code.

What about free functions for objects?

Our reference-counting mechanism wants you to have a free function for your object. If your object has nothing to free as sub-resources (like a string or a file descriptor), you can just pass standard libraryā€™s free (from stdlib.h) function to ref_counted_init.

Here is an example of how to use reference counting:

Simple Reference Counted Object

An object must have these things:

  • A struct definition and a type for it.
  • A constructor function to create the object. With a namespace structure like ..._new() is a good practice.
  • A destructor function to free the object. With a namespace structure like ..._free().

Example cat.c

#include <stdio.h>
#include <stdlib.h>

#include "ref.h"

typedef struct cat {
    ref_counted_t ref_counted;
    char* name;
    int age;
} cat_t;

void cat_free(void* obj) {
    printf("* Freeing cat_t object...\n");
    
    cat_t* cat = (cat_t*)obj;
    free(cat->name);
    free(cat);
}

cat_t* cat_new(const char* name, int age) {
    printf("* Creating cat_t object...\n");
    
    cat_t* cat = malloc(sizeof(cat_t));
    ref_counted_init(&cat->ref_counted, cat_free);
    cat->name = strdup(name);
    cat->age = age;
    return cat;
}

int main() {
    cat_t* cat = cat_new("Meowing Cat", 32);
    ref_counted_use(&cat->ref_counted);

    printf("Cat's age: %d\n", cat->age);
    printf("Reference count: %d\n\n", cat->ref_counted.count);

    ref_counted_leave((void**)&cat, &cat->ref_counted);

    return 0;
}

In this example, we have a struct cat with a name and an age field. We have a cat_new function to create a new cat object and a cat_free function to free the object.

We use ref_counted_init to initialize the reference count and set the deallocation function to cat_free. We use ref_counted_use to increase the reference count and ref_counted_leave to decrease the reference count and deallocate the object when the reference count reaches zero.

The example creates a cat object, increases the reference count, prints the catā€™s age and the reference count, and then decreases the reference count and deallocates the object.

The expected output is:

* Creating cat_t object...
Cat's age: 32
Reference count: 1

* Freeing cat_t object...

As you can see, the object is created, used, and then deallocated when the reference count reaches zero.

And thatā€™s it! You have implemented reference counting in C to manage memory and avoid memory leaks. šŸ˜ŗ

Important! Donā€™t forget to compile your code with -Wall -Wextra -Werror flags to catch any warnings and errors.

Important! Keep in mind that when you free a dynamic allocated object (like with malloc), you should not be trying to use it in any case or you should never trust to look into it in your debugger! Because, when you free an object, your operating system can give that memory to another process or use it for another purpose. So, you can see very unexpected results when you try to look into it. Please check Undefined Behavior article on Wikipedia.


Thatā€™s all, Cat?

Noā€¦ There are also atomicity for multi-threaded and complicated apps/services! But, cat will teach you that in another tutorial!

Also, cat will teach you how to write cute C macros to make this reference counting mechanism more pwogwammer-fwiendly! šŸ¾

For now, you can use this simple reference counting mechanism in your C projects to manage memory and avoid memory leaks.


Conclusion

Reference counting is a powerful memory management technique that can help you manage memory in C without worrying about memory leaks. With reference counting, you can write complicated apps/services in C just like with the comfort of scripting.

I hope you enjoyed this tutorial and learned something new. If you have any questions or feedback, feel free to ask; you can reach me on GDBFrontend Discord.

Thank you for reading! Happy code pawing! šŸ¾