While developing the Orion kernel, I found a resource leak that was caused by poor reference tracking.
To fix this resource leak, I started work on small library service that would implement all of the common functionality needed for resource management.
Originally, this work just focused on reference counting and de-allocating objects, but grew to encompass constructors and destructors, as well as in-built objects for managing groups of other objects.
This Object Manager became one of the core components of the Nucleus kernel, the successor to the Orion kernel.
I moved that code into a separate library that could be used in user-space, so I could make use of it when developing other applications.
Since the BLOC library was designed for use in the Orion operating system, though it can be used on any other, the documentation for it can be found in the appropriate section of the Orion documentation.
This includes all information about the features it provides to programs that use it, including both the object interface, and details of the in-built objects.
The library currently includes List, Tree, and Iterator objects, since they provide commonly needed functionality.
The details of the internal implementation can be found in the code.
Usage
Object oriented languages provide a number of language features to aid the programmer in the implementation of their program.
This library provides the run-time support for these features in a language without them: C.
The programmer can make the most of them by embracing a certain style of programming, without forfeiting the advantages of C.
Every object should be entirely self-contained.
The implementation of an object should provide not just the structure, but all of the supporting routines that manage and operate on instances of the object.
To this end, every type of object should be implemented individually, either in its own source file, or as a set of source files grouped together.
These files should contain all of the object's "methods", both public and private, typically starting with the name of the object (e.g. foo_create).
Private methods could be named with a prefixing underscore (e.g. _foo_get_name), and can where appropriate be declared as static functions.
If the implementation is split across multiple files, a private local header file may be needed to share these functions internally.
All objects will also need to have a public header file, although this can be shared, which defines any public details they may have.
This would include the prototypes for the public methods, as well as any public structure.
If all of the attributes are public, then this structure will suffice.
If all of the attributes are private, it can be declared in the previously mentioned internal header file, or in the single source file if applicable.
If a mix of public and private attributes are required, the private definition of the structure can contain an embedded version of the public structure at the beginning.
Whenever a reference is taken to an object (i.e. it is assigned to a pointer variable), the obj_get function should be used to notify the library about the reference.
When this reference is dropped, the obj_put function, or one of the drop or swap macros should be used for the same reason.
When the last reference to an object is dropped, the library will free its resources.
If an object holds any references itself, these should all be dropped in the object's deconstructor function.
The library also provides a way to briefly lock (and unlock) objects, to ensure certain operations are performed atomically, and are thread-safe.
This is just a simple spinlock, so it should not be held for long periods of time.
It is implemented as a recursive spinlock, so it is safe to call other functions that may lock the object while holding the lock.
The lock can only guarantee atomic operations if the object is always locked before changing.
Any code that modifies the object without taking the lock could run concurrently to code that is holding the lock.
List objects are provided for keeping collections of objects, potentially all of one type.
Tree objects tackle a similar problem, but are better for ordered and frequently searched data.
The standard operations are for creating, adding to, removing from, and counting the number of entries in these objects.
Lists also provide functions for popping objects from the start and end, which enable them to be used as stacks and queues.
Both objects can be traversed with the use of an Iterator object.
Custom iterable objects can be implemented by embedding the Iterable family of structures in the object.
GCC Plug-in
This feature is not public yet and is still being tested
Even though the library provides utility functions for managing the references to objects, these still have to be littered throughout the code of a project manually.
This task is something that needs attention and can still be done wrong.
To help with this, I developed a plug-in for GCC that can automatically apply the library to a codebase.
While object types still must be declared manually using the library's facilities, and objects still must be created using the library, the plug-in can insert all of the necessary reference counting calls.
The plug-in implements the object attribute, which can be applied to variables, structure/union members, functions and function parameters.
Anything marked with this attribute will be inspected by the plug-in at compile time, and it will ensure that all of these objects are correctly referenced.
Every time an object is assigned to a variable or structure/union member the old value will have its reference count decreased, and the new value will have its reference count increased.
When a function that returns an object returns, the return value has its reference count increased.
When a function returns, all local variables (except static ones) will have their reference counts decreased.
When a function with object parameters is called, the values passed to the parameters have their reference count increased prior to calling, and decreased afterwards.
When a function that returns a reference is assigned to an object variable/member/parameter, the target adopts the reference from the function rather than obtaining its own.
This means a program can be written in a normal manner without much consideration given to the application of the library.
The resulting binary will take full advantage of the reference counting provided by the library and will do so in a multi-thread safe way.
Using this plug-in can reduce the amount of code required to implement a system, but means the project cannot be compiled without the plug-in.
If the program is compiled without the plug-in then the result will function, but would never release memory as each object would just maintain a reference count of one.
Technical
Under the hood, the library uses a very simple system of storing some book-keeping information just before the object's body in memory.
It allocates enough space for this whenever an object is instantiated, and maintains it through to when the object is destroyed.
The program using the library should never interfere with this internal structure.
It stores information about how many references the object currently has, and about the type of object.
This is used to ensure when the last reference to an object is dropped, the proper deconstructor is called, avoiding any manual resource management for the programmer.
Objects also each contain a recursive spinlock, which can be held by one thread at a time.
List and Tree objects are implemented with entry and node structures, which each contain a pointer to the object.
This allows objects to be a part of multiple lists and trees, or be in the same list multiple times.
These objects make heavy use of the Iterable structures, with lists not particularly containing much else.
Any object that uses this set structure, which just implements a generic doubly-linked list, can be traversed by Iterator objects.
Trees use this to hold links between the nodes in logical order, as opposed to the order they need to keep internally.
This requires a small amount of extra work, but means they can be iterated easily, and it makes it faster to find the successor of a node in the event that is needed when deleting a node.
Iterator objects internally track their state (e.g. position), and provide a simple interface for programs to use.
This interface deals almost entirely with relative and absolute movement withing an iterable structure.
It is not always necessary to use this directly, since it also provides a foreach macro, with syntax similar to a standard for loop.
This uses the public interfaces under the hood to traverse from the start of a list to the end.