Technique: Encapsulation and Information Hiding in C

Encapsulation is usually associated with object-oriented programming languages, and information hiding is not straightforward to achieve in C. However, these two principles can still be applied by creating interfaces that work on “opaque” structures.

The first step is to forward-declare a structure in a header file. This structure will NOT have a definition in the header file, so users cannot declare this type directly in their code. They can only work with pointers to this type.

// Opaque circular buffer structure
typedef struct circular_buf_t circular_buf_t;

The definition for this structure will be kept inside of the corresponding .c file. This will ensure that we can change the structure definition as needed, without requiring end users to update their code. The users only need to know that this type of structure exists.

We often provide a corresponding “handle” type that is mapped as a pointer to this opaque structure, a void *, or a uintptr_t. This is a better indication that the user cannot directly dereference the type.

// Handle type, the way users interact with the API
typedef circular_buf_t* cbuf_handle_t;
Note

If not using a pointer to the structure as the handle type, then inside of the implementation you would cast to the appropriate type.

You could also completely eliminate the struct forward definition and instead only supply a generic void * or uintptr_t handle type.

All public interfaces will be declared in the header file and must operate on the handle type. All private interfaces and data will be defined in the corresponding .c file so they are not visible to consumers of the header.

When working with opaque types, an initialization function is often required in order to generate a valid handle.

/// Pass in a storage buffer and size 
/// Returns a circular buffer handle 
cbuf_handle_t circular_buf_init(uint8_t* buffer, size_t size);
Note

Recall that since our opaque struct has no public definition in the .h file, users cannot declare these structures on their own. If you do not want to utilize dynamic memory allocation (common in embedded software), you can instead statically pre-allocate a pool of objects inside of your .c file. Be sure to assert or return a NULL value if the assignment fails because all pre-allocated objects are in use. Also indicate to users that they should check for failure.

That handle is then passed as an input parameter to all public functions associated with the opaque type:

/// Reset the circular buffer to empty, head == tail
void circular_buf_reset(cbuf_handle_t me);

/// Put version 1 continues to add data if the buffer is full
/// Old data is overwritten
void circular_buf_put(cbuf_handle_t me, uint8_t data);

A “destructor” is usually required in order to free any previously allocated data:

/// Free a circular buffer structure. 
/// Does not free data buffer; owner is responsible for that 
void circular_buf_free(cbuf_handle_t me);

Summary

  • Forward-declare structures or provide handle typedefs to users
  • Declare an initialization function that can generate an “object” of the appropriate type and return the handle to the user
  • Declare a destructor that can free any memory allocated to the “object” with the associated handle
  • All public interfaces are declared in the header file and operate on the handle type
  • All private interfaces and data is declared within the .c file (without extern!) so that external modules cannot access them

References

Insights from Writing an Embedded IoT App (Almost) Entirely in Rust

Today we have a guest post from Louis Thiery. Louis has spent many years building hardware and software for IoT devices ranging from agriculture to consumer electronics. He now develops IoT infrastructure at Helium. Helium is the largest public LPWAN in the US and is shipping now (Sept 2020) in Europe. You can contact him by email. …

Paper: Very Short Functions are a Code Smell

18 September 2020 by Phillip Johnston • Last updated 9 May 2022 It’s always good to review sources that challenge some of the ideas you hold dear. This is why I was intrigued to find a blog post called “Very short functions are a code smell – an overview of the science on function length”. One of the ideas that I operate under is that smaller function are superior to longer functions, primarily because they are easier to reason about. This is not a unique or new position; as the authors point out, we can find references as far back …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Paper: What Every Systems Programmer Should Know About Lockless Concurrency

21 May 2020 by Phillip Johnston • Last updated 15 August 2023 “What Every Systems Programmer Should Know About Lockless Concurrency” is a short paper by Matt Kline that explores ways we can synchronize concurrent operations without relying on standard OS constructs, such as mutexes, semaphores, and condition variables. These strategies are also used to implement those same OS constructs. The paper also briefly touches on sources of concurrency problems that arise from compilers and from the hardware itself. Note: This paper was featured as a Reading Club assignment. Files What Every Systems Programmer Should Know About Lockless Concurrency Archive …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Lockless Concurrency

21 May 2020 by Phillip Johnston Lockless concurrency describes methods for synchronizing concurrent operations without relying on standard OS constructs, such as mutexes, semaphores, and condition variables. The same strategies are also used to implement those OS constructs. Situations requiring lockless concurrency tend to pop up most often in the embedded domain and in OS kernel development. These types of development involve interrupt service routines, real-time processing, and cases where blocking would be disastrous. In other cases, code may need to be synchronized before an OS is fully available, or without an OS at all. Under such conditions, a lock-free …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Backward Compatiblity: APIs

19 May 2020 by Phillip Johnston • Last updated 14 December 2021 When maintaining an API, it is important to provide stability for your users. This guide provides rules of thumb and strategies that you can use to keep your systems backward compatible. Table of Contents: Define a Deprecation Strategy Compatibility Rules of Thumb Strategies for Maintaining Backward Compatibility Use Explicit Versioning Add Dynamic Capability Discovery Add New Optional Parameters or New Methods Introduce New Parameters or New Data Types in New Methods Create Wrappers Further Reading Define a Deprecation Strategy Systems and APIs evolve over time, and we must …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Implementing Stack Smashing Protection for Microcontrollers (and Embedded Artistry’s libc)

Stack buffer overflows are a category of error that can wreak havoc on our programs, resulting in sporadic crashes or strange and unexpected program behaviors. A stack buffer overflow occurs when a program writes to a memory address on the stack which is outside of its current stack frame, often triggered by a buffer overflow on a local …

Shell Script Debugging Techniques

8 May 2020 by Phillip Johnston • Last updated 14 December 2021 I recently ran into a strange problem writing a Bash script. I had a command that didn’t work within a shell script. I used echo to print the command out, then ran it in the terminal, and it worked perfectly. # copied output works, shows me the files I’m expecting echo find $DIRS $EXCLUDES -type f $FILE_TYPES # But this does nothing! find $DIRS $EXCLUDES -type f $FILE_TYPES Debugging this problem was quite difficult, until I learned about a variety of helpful techniques. set The set command can …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.