Technique: Objects in C

While C is not an object-oriented language, objects can be implemented using structures.

Common first-pass approaches to library implementations involve APIs that operate on static library data. We typically reach a point where we need our library interfaces to work on any number of instances, instead of just internal static instances.

Objects can be thought of as structures, and member functions are those which take a pointer to the particular structure which represents the instance that should be operated on. The structure should contain all of the relevant data that should be contained with an object.

For example, consider a circular buffer structure:

struct circular_buf_t {
	uint8_t * buffer;
	size_t head;
	size_t tail;
	size_t max; //of the buffer
	bool full;
};

To ensure our C APIs can be used with multiple objects, we need to:

  1. Ensure that all mutable configuration options and context-specific information (e.g., state) in stored in the structure
  2. Ensure that the necessary APIs take a pointer to the structure (to avoid copies and enable state modifications if necessary)
    1. Functions that do not modify the structure should specify the input parameter as const for clarity

To support multiple objects, we would declare a struct instance for each object. We invoke the associated APIs with whatever struct we need to use at that time.

Our APIs are then adjusted to take a structure pointer as the first parameter. For example:

/// Retrieve a value from the buffer
/// Returns 0 on success, -1 if the buffer is empty
int circular_buf_get(struct circular_buf_t* me, uint8_t * data);

References

4 Replies to “Technique: Objects in C”

  1. The “_t” suffix for types is reserved by POSIX and is generally not advised for application-specific types.

    Also, the naming convention for class operations (like “circular_buf_get()”) can be strengthened by consistently naming the pointer to the associated attribute structure “me” (instead of “cbuf’). This will directly correspond to the “this” pointer in C++/Java or “self” reference in Python. I’ve been advocating to call this pointer “me” (and published code with this naming convention since 2000) and I see many people adapting this convention. A good alternative is “self”. But the point is that inventing a name for this pointer (like “cbuf”) for each class is inconsistent and is a lost opportunity to make the special nature of this pointer more apparent.

  2. Thanks for the feedback, I agree ‘me’ is a better name and I will adjust that across the site.

    I will have to think more on whether or not I really feel motivated by the POSIX reservation of _t. This convention has been practically universal in code bases I’ve worked in over the last 10 years, and I rarely see an embedded code base that uses the POSIX environment, and whenever I have used POSIX, it is usually constrained behind a general abstraction where its exposure is limited.

    Do you use an alternate suffix to denote type names? Or do you just not worry about that?

  3. Now, with respect to _t, one area I do think is particularly problematic is something like mutex_t, because that is something more likely to collide and be used in a place that enables the collusion. I suspect I have avoided this problem in practice because of name spacing in C++.

  4. This convention (“_t” suffix for types) has been practically universal…

    Yes, I see the suffix “_t” for types used frequently. For example, the widely adapted BARR-C:2018 coding standard for embedded C has the following rule 5.1a : “The names of all new data types, including structures, unions, and enumerations, shall consist only of lowercase characters and internal underscores and end with ‘_t’. ”

    Do you use an alternate suffix to denote type names? Or do you just not worry about that?

    No, I don’t use any specific suffixes for types. Instead, class names (struct in C) start with a capital letter (e.g., QActive class for active objects). This convention reflects the fact that class names correspond to nouns, and nouns are always capitalized in German. This makes class types (and class operations, like QActive_subscribe() ) stand out in the code.

Share Your Thoughts

This site uses Akismet to reduce spam. Learn how your comment data is processed.