1 - Beginnings
3 - Hello World
4 - More On GPIO
5 - Interrupts
7 - Timers
10 - Buttons and Bouncing
Would you like to be notified by email when Stephen Friederichs publishes a new blog?
Strictly speaking, C is not an object-oriented language. Although it provides some features that fit into the object-oriented paradigm it has never had the full object-oriented focus that its successor C++ offers. Although it’s far from my favorite language C++ introduced some very useful concepts and abilities that I miss when I’m developing in ANSI C. One such concept is protected member variables and functions.
When you declare a class in C++ you can also declare member variables and functions as part of that class. Often, these members contain and manipulate internal information about the state of the class and aren’t meant to be accessed or modified by code external to the class. C++ allows you to declare these member variables in a way that code outside the class has limited access to them. There are various levels: the variable may be completely hidden, it may be read-only or it could be fully-accessible but only to other specific code (a “friend” class) that needs to access it. Hiding internal implementation details in this way is considered good practice for several reasons: it produces cleaner, better organized code, it improves security and stability by preventing code (malicious or otherwise) from corrupting the internal state of an object and it allow and separates a class’s interface (which should ideally change as little as possible) and its implementation (which may change due to bugs, security issues, performance improvements, etc.). C doesn’t allow the same level of control as C++ but there is still a way to hide implementation details in C.
I’ve uploaded an example of a First-In-First-Out (FIFO) style stack to the code snippets section. In the real world there would be a header file advertising the public functions and variables for the stack and a source file that contains the implementation. For the sake of the example I had to put everything in the same ‘file’. The comments show where the header and source parts begin and end. However, to demonstrate the fact that the internal implementation can change while the public interface remains the same there are two separate source files in the code separated by preprocessor defines. One implementation uses a stack that grows up, the other grows down. The header file is the same for both implementations. The point to take home is that if you relied on accessing the internal structure of the stack object you might eventually be disappointed if the internal implementation changes.
There are two features C offers to help hide data. The first feature is that you can declare a pointer to a struct that hasn’t yet been defined. This is used in the example to declare a typedef for the stack type that is a pointer to a stack object struct without declaring the stack object struct. The struct type is declared inside the source file. Since the header file doesn’t contain the struct definition other programmers don’t know the internal structure of the type and can’t manipulate the struct directly. Instead they have to rely on the public functions declared in the header file.
The other feature used in this example is the use of the typing system in C to ensure that stack objects can’t be modified directly. The stack type is defined as a pointer to a const stack struct object. This ensures that a programmer can’t accidentally modify the stack object without angering the compiler. Structs in C are just different variables stored together in memory. The pointer to the struct is simply the address of the first variable in the struct. A programmer could accidentally modify the contents of the struct by assigning a value to the de-referenced stack object. By defining the stack type as pointing to a const stack struct, the compiler will complain if a programmer tries to directly modify the stack object.
This isn’t without drawbacks. It forces all of the functions that need to write to the object to recast the object as a non-const pointer. By the same token, any programmer who desires to modify the internals of the object can simply cast the pointer to the same type. As a safeguard against a unintentional modifications (malicious or otherwise) the stack struct contains a canary variable. This entry is set to a predetermined value when the object is initialized and every function that accesses the object checks it to ensure that it’s set to the correct value. When the object is destroyed the canary is cleared to ensure that if there are any pointers to the object remaining, any calls made with the pointer will fail.
One final note - C offers the capability to hide functions as well as data. Any functions declared as a static are to be used only within that file. The compiler enforces this rule. In the provided example, the _stack_valid function can only to be used within the stack.c file. If you attempt to use it outside of that file the compiler will complain. The _stack_valid function is a private function for the stack class.
Obviously this approach doesn’t have the versatility of C++. You can’t define members of objects as accessible by friend classes or even declare object members as public. However, this approach lets you hide the internal workings of classes to ensure that anyone else who uses your code utilizes only the publicly-available interface and doesn’t fool around with the internal implementation of your of your objects. This allows you to enjoy the benefits of data hiding without needing to use C++.
Add a Comment