Dr. Dobb's Journal, January 2006

Single Inheritance Classes in C

An embedded- system perspective

By Ron Kreymborg

Ron designs software and hardware for embedded systems. He can be reached at ron@jennaron.com.au.

I write a lot of programs for embedded systems, and I prefer to program in C++ whenever possible. However, for some smaller processors, a C++ compiler either doesn't exist or else is expensive. Recently, I needed to port an existing C++ project to a processor for which I had only a C compiler. The project had benefited greatly from using an inheritable class structure, so I decided to try and develop a mechanism for building inheritable classes in C.

I am not the first to do this, of course, and others have published successful methods. Miro Samek, for instance, developed a set of macros that hide the C code and allow a C++-like syntax (see Practical State Charts in C/C++, by Miro Samek, CMP Books, 2002). Hekon Hallingstad's class implementation (http://www.pvv.ntnu.no/~hakonhal/main.cgi/c/classes) used the offsetof() macro to access parent class data. Dan Saks described using C abstract types as classes (see "Abstract Types Using C," by Dan Saks, Embedded Systems Programming, October 2003). However, I had developed my own standalone class software over the years and my recent work was able to extend this to support classes with single inheritance.

For want of a better name, I use the name of the header file—C_Classes. Where would you use inheritance in embedded systems? Think of manufacturers building a range of chemical process controllers. They all have the same liquid crystal display, analog interfaces, power supply, RS-232 and radio interface, and similar sets of buttons and switches. The software interface to this hardware is also going to be similar. In this article, I introduce inheritance by defining a set of base classes that have drivers for all this hardware and well defined interfaces. Thus, a range of instruments can use the same base class set and only need a set of derived classes that characterize the particular functionality and set of interfaces that a new instrument will use. This makes for straightforward instrument development.

A Simple Class Structure

C++ hides the class data structure within itself and provides strict rules for how it can be accessed. You don't have this luxury in C, but you can create something similar by defining a single structure to contain all the data belonging to the class.

For example, I use my C_Classes method to define a skeleton class that could be the basis for a motor controller. The class is Motor and it controls only the speed and direction. The microprocessor I am programming has a number of ports that are connected to a number of motor controller chips. To set a motor's speed, you need only write the direction and revolutions-per-minute (RPM) to its respective ports. To keep things simple, I just use integers.

The class data structure is defined in the Motor class header as a typedef:

typedef struct
{
int* mPort;
int mSpeed;
DIRECTION mDirection;
} MotorData;

The MotorData structure is the definition template for the Motor class data. In C++, these variables would be declared in a private section of the class definition. The mPort class data variable is assigned the port address for a particular motor. This lets the same Motor class code manage several motors in the one application.

The DIRECTION type is a simple enum:

typedef enum
{
DR_UNKNOWN,
DR_CCW, // counter-clockwise
DR_CW, // clockwise
} DIRECTION;

As in C++, the class has a constructor and a destructor. Creating an instance of the class is a call to the constructor using the statement:

MotorData* dMotor = MotorCtor();

The constructor creates and initializes an instance of the MotorData structure just defined, then returns a pointer to it that is assigned to the dMotor class data pointer. Subsequently, the caller passes this pointer to the class at every method call. This is the technique the C++ compiler uses, only it does it under the covers. The class contains no instance data within itself: It just provides methods to manipulate the passed-in data.

Okay, that looks simple enough, but how do you call a class method?

Because I am developing a way to create inheritable classes, I need to be able to call methods in a base class through a derived class, add additional methods in that derived class, and override base class methods in the derived class if required. To do this, I build a table of function pointers in RAM that provides access to the public methods of the class. I create the definition of this table of vectors (or vtable) as another typedef that includes function pointer prototypes for each public method. In the Motor example, you need to be able to initialize the port address, and set/get the speed and direction, all via the class interface, so that makes five methods; see Example 1. Along with the vtable definition, the class header also includes the definition for a pointer to the vtable and prototypes for the constructor and destructor. These are all publicly accessible:

extern MOTOR* Motor;
extern MotorData* MotorCtor(void);
extern void MotorXtor(MotorData*);

Part of the constructor's job is to build in RAM the vtable containing the five function pointers and set the public class vtable pointer (Motor) to point to that location. After a call to the constructor, users of this class can call any of the public methods through the class pointer. For example, setting the motor speed to 450 RPM, and remembering you also need to pass in the class data pointer, would be the call:

Motor->SetSpeed(dMotor,450);

While the owner of the dMotor structure has no business ever looking inside it, after this call you would know that dMotor->mSpeed has a value equivalent to 450 RPM.

The Constructor

So how does the class constructor build the vtable and create the data instance?

Again, the header file contains a template of the vtable (the MOTOR typedef). Within the class code file, an instance of this vtable is defined and this instance is copied from ROM to RAM. Continuing the example, in the Motor code file, the vtable instance along with the global class pointer declaration looks like Example 2(a). It is crucial, of course, that the instance has the same order as the template.

The constructor function looks like Example 2(b). If there is a possibility that the class will have several instances, or be created and destroyed several times, then the class must ensure there is always only one vtable in RAM. To this end, every class has InstCount as a private variable that is used to keep track of the number of instances extant for this class. It is incremented during construction and decremented during destruction. The actual copying is conveniently done by the CREATE_VTABLE macro. This uses cMotor as a source, Motor as a destination, and the MOTOR structure size for both heap allocation and for the number of bytes to copy.

As an aside, my original C_Classes version for standalone classes used the simple Motor = &cMotor instead of the macro. You can still do this for standalone classes if RAM is in short supply.

Again, the constructor must also create and initialize the class data on the heap for every instantiation. To this end, the constructor creates a pointer that is returned to each individual caller to manage during the life of the class instance. The Allocate function is defined in the C_Classes header and is a simple wrapper around malloc that includes an internal allocation counter. The counter can be interrogated at any time and can be useful during debugging.

That's it for the class constructor: It must make sure the vtable is set up in RAM and initializes the class data. However, as you'll see, a derived class requires a little more work.

The Destructor

The code for a normal destructor is straightforward. If the data pointer is valid, the motor is forced to a stop and the actual disposal code is just two lines; see Example 3. The class data is destroyed when the destructor calls the Free function. This is defined in the C_Classes header, and like the Allocate function, is a wrapper around the C library malloc/free heap memory management. Free decrements the internal counter that was incremented by Allocate.

The vtable class's destruction is handled by the DestroyVtable function that is also defined in the C_Classes header. While the constructor increments the class InstCount variable, it is decremented in DestroyVtable. The counter going to zero implies that this is the last instance of the class to be destroyed, and consequently, the vtable is deleted from RAM by a call to Free.

All functions in a class code file are declared as static—the only access is via the class pointer. Listings One and Two show the C_Classes header and code files. The Motor class header (plain.h) and code files (plain.c) are available electronically; see "Resource Center," page 4).

The vtable Copy Macro

I try not to use macros very often, but for the vtable, copiers save typing a lot of fiddly and error-prone syntax. The definition in the C_Classes header is:

#define CREATE_VTABLE(VECTOR,
STRUCTURE, TYPEDEF) \
VECTOR = (TYPEDEF*)Allocate
(sizeof(TYPEDEF)); \
memcpy(VECTOR, &STRUCTURE,
sizeof(TYPEDEF))

It allocates space on the heap for the structure, assigning it to the VECTOR pointer, then copies the class vtable instance STRUCTURE from ROM to this space in the heap.

Data Hiding

At this point, C++ programmers are grumbling that I have exposed the class's data by publishing the MotorData structure in the header. This is true. There is nothing to stop users of this class typing a statement like:

newSpeed = MotorData->speed;

rather than calling the appropriate method, and so blowing away the very idea of objects. For example, what if the Motor class provided a revolutions-per-minute interface but internally used radians-per-second? To derive from a base class, it must somehow publish its data structure. However, an ordinary class need not. You simply move the data definition into the code file and replace all exposed references to the data structure with void pointers. From the examples so far, the SetSpeed definition in the header would then look like:

void (*SetSpeed)(void*, int);

and the example constructor, method call, and destructor would look like:

void* MotorData = MotorCtor();
Motor->SetSpeed(dMotor, 450);
MotorXtor(dMotor);

Externally, the data pointer is now useless, but within the code file, a simple macro is used to cast the void pointer. The source-code listings (available electronically) all include the ME macro for reference. Its use in the aforementioned SetSpeed method would be:

void SetSpeed(void* d,int speed)
{
ME(d)->mSpeed = speed;
}

Of course, a little source-code sticky-nosing would allow any programmer to learn the class data structure, but I am assuming that those using these methods would not willfully bypass their intent, and using void pointers makes accidental use very unlikely. The downside is you lose the structure cross-checking capability of the compiler.

An Inheritable Class Structure

Inheritance in a C++ context means the ability to both derive a new class from a base class that adds new capability, and perhaps to change some functional aspect of the base class. While C++ has a syntax for this, you must do it yourself in C. The intent of my implementation is to minimize RAM usage and execution time, the former is usually a scarce resource in small microprocessors.

The header file for a derived class includes the same typedef for the class methods as described earlier, but in addition, it now also includes the methods from the base class that are to be available to users of the derived class. I stay with the motor example and derive a class that provides some additional capability, like setting an acceleration rate.

You first need to define the data structure for the derived class. This must be usable by both the derived and base classes. The mechanism I use is based on the fact that, if the base class data structure is the first entry in the derived class data structure, pointers to both a derived and a base class will point at the same data. While this limits the technique to just deriving from one base class (single inheritance), it is rare to find a requirement for multiple inheritance in small embedded systems.

Our derived class data needs to store the acceleration rate as well, so the class data structure becomes Example 4(a). The typedef for the derived class vtable, along with the constructor/destructor and class vtable pointer definitions, looks like Example 4(b). The MotorControl class vtable pointer provides access to this class in the same way the Motor pointer did for the Motor class. We can use the same technique as before to initialize this pointer, but how do we provide both access to the base class methods and have it initialize our data structure?

Like the Motor class, the MotorControl class defines an instance of the vtable in its code file:

static const MOTOR_CONTROL
cMotorControl = {
SetNewSpeed
};
MOTOR_CONTROL* MotorControl;

But wait! Where are the base class vectors listed in the typedef?

Step back and think about what is required in a derived class. You don't want the ROM overhead of same name functions in the derived class that just pass execution on to the base class. What you do want is for the derived class to be able to call the base class and have it initialize both the derived class's vtable and its base class data, and from then on just provide its methods for use until destructor time.

Harking back to the Motor class constructor, I called a macro to construct a copy of the vtable instance in RAM. If I use the same macro for the derived class, what happens?

CREATE_VTABLE(MotorControl,
cMotorControl, MOTOR_CONTROL);

Because it uses the size of the MOTOR_CONTROL structure, it copies the SetNewSpeed vector and leaves empty space for the other five vectors. If you now pass to the base class a pointer to where the first base class method should be in the MotorControl vtable, it can simply copy the addresses of these methods one after another back into the derived class's vtable. In the MotorControl example, the first base class method is InitPort, and if you take its address and pass it to the base class, the mechanism is rather simple. In the derived class:

void (**v)() = &(MotorControl->InitPort);

and in the base class:

*v++ = InitPort;
*v++ = SetSpeed;
etc.

You also need the base class to initialize the base class data in the derived class. These requirements mean that, for the base class to initialize the derived class, you must pass to the base class constructor a pointer to the data structure in the derived class and a pointer to the first of the base class vectors in the derived class RAM vtable. Example 5, a revised Motor constructor to do just that, fills out the derived class RAM vtable with pointers to the base class methods. The derived class data pointer is assigned to the local pointer and subsequently the constructor initializes the data back in the derived class. On the other hand, passing in a couple of nulls sets up a standalone class. (This demonstration constructor is configured to build both derived classes and instances of itself; a rare situation in practice, and a true base class would leave out the standalone code.)

If you now call the MotorControl SetSpeed method, it directly executes that method in the base class.

The derived class can override a base class method by overwriting the associated vector in the vtable; for example, if the derived class wanted to override the base class SetDirection method. Then, after the vtable has been built, it could replace that method with a statement such as:

MotorControl->SetDirection =
MySetDirection;

If access to the base class version is still required, you can declare a static function pointer for saving the base class pointer prior to the replacement. Example 6 is the example derived class constructor.

Because in C_Classes the base class for a derived class contains nothing other than the code, the same base class can support a number of derived classes in the one application. In the example I present here, as well as this derived class that provides controlled acceleration for one motor, another derived class could provide pulse width control for a motor attached to a peristaltic pump, with the control input parameter being liters-per-hour.

Motor.h and motor.c are the header and code files (available electronically) for the now modified Motor base class, while MotorControl.h and MotorControl.c show the new MotorControl class. MotorVtable.h is the Motor base class vtable definition in a form that can be included in both the base class itself and any derived classes. Using this technique means there are only two places the programmer must ensure correspondence—the class definition and its instance in the class code file (although it does mean the compiler gives incompatible types warnings). Main.c (available electronically) is a demonstration main program showing examples of using these classes. The ports in the microprocessor are simulated as global integers. It is instructive to create a project in something like Visual C and step through the entire program.

Compiler Dialects

The example listings I've presented are for Microsoft's VC7 compiler, mainly because the Visual C/C++ package is fairly common and has an excellent debugger. Other compilers may require small changes. For example, the GCC compiler versions typically provide the memcpy prototype in the string.h header file rather than the memory.h file.

Most compilers will give you incompatible type warnings when referencing parental structures with derived class names. While these can be safely ignored, they can also be simply fixed with a cast to the base class structure; for example:

MotorXtor((MotorData*)d);

I use the standard library memcpy in the macros. On some microprocessors, particularly those with a Harvard architecture, the compiler may require special directions when declaring the class vtable instance and copying it from ROM to RAM. Check this out as, otherwise, you will find the compiler copies the instance to RAM before your program runs as part of data initialization, and then memcpy simply copies it from one place in RAM to another.

Conclusion

I have presented a fairly simple method for creating classes in C. The code is transparent and regular, in that the method can apply to both standalone and inheritable classes with no change in syntax and little additional code. An inherited class need only call the base class constructor to be fully initialized. The ROM footprint is hardly different from a carefully modularized traditional C method, with the advantage of an obvious class structure.

While not a problem in itself, the fact that the class vtable definition and its instance are in two different places means that you must take care they always match. This is particularly so when adding new functions to an existing design.

In a typical small embedded system, classes are created early and rarely destroyed, in part to ensure the heap does not become fragmented over the months or years between resets, but also because in most cases all classes are in use all the time. If your app is dynamically creating and destroying classes, you need to be sure of your compiler's heap management. My recommendation is to get all heap setup over and done with during initialization.

DDJ



Listing One
/******************************************************************************
   PROJECT  C Classes
   NAME     C_Classes.h
   PURPOSE  A header file for C classes derived using the C_Class methods.
   AUTHOR   Ron Kreymborg, Jennaron Research
******************************************************************************/
#ifndef _C_CLASSES_H
#define _C_CLASSES_H

#include <stdlib.h>
#include <memory.h>

#define TRUE        1
#define FALSE       0
//-----------------------------------------------------------------------------
// A macro to copy the vtable structure in rom into ram. The VECTOR parameter
// is the destination structure pointer, and the STRUCTURE parameter is a
// pointer to the source vector table. Note that sizeof will ensure there is
// space for all functions defined in the typedef.
//
#define CREATE_VTABLE(VECTOR, STRUCTURE, TYPEDEF) \
        VECTOR = (TYPEDEF*)Allocate(sizeof(TYPEDEF)); \
        memcpy(VECTOR, &STRUCTURE, sizeof(TYPEDEF))
/*
If you want to replace the macro with a function, here is the 
required prototype...
void CREATE_VTABLE(void** to, void* from, int size);
*/
//-----------------------------------------------------------------------------
// Free the vtable heap memory based on usage. Both parameters are pointers.
//
extern void DestroyVtable(void*, int*);
//-----------------------------------------------------------------------------
// Allocate space from the heap.
//
extern void* Allocate(int);
//-----------------------------------------------------------------------------
// Return the memory to the heap.
//
extern void Free(void*);
//-----------------------------------------------------------------------------
// Return the current count of allocated items on the heap. Call when the
// process completes to check if all classes have been correctly shutdown.
//
extern int DataCount(void);

#endif  // _C_CLASSES_H
Back to article


Listing Two
/******************************************************************************
   PROJECT  C Classes
   NAME     C_Classes.c
   PURPOSE  The C_Classes vtable heap allocation and de-allocation methods.
   AUTHOR   Ron Kreymborg, Jennaron Research
******************************************************************************/
#include <stdio.h>
#include "C_Classes.h"

static int mDataCount;
//-----------------------------------------------------------------------------
// Destroy the vtable based on whether the instance count goes to zero.
//
void DestroyVtable(void* structure, int* count)
{
    if (--(*count) == 0)
    {
        Free(structure);
    }
}
//-----------------------------------------------------------------------------
// Remove a heap structure.
//
void Free(void *p)
{
    mDataCount--;
    free(p);
}
//-----------------------------------------------------------------------------
// Allocate the requested bytes from the heap.
//
void *Allocate(int size)
{
    void *p = malloc(size);
    if (p)
    {
        mDataCount++;
        return p;
    }
    else
    {
        // The heap space is exhausted. Provide an error handler here. Do NOT 
        // ever return from here unless the heap has been enlarged. Put a 
        // suitable error handler here that matches the processor environment 
        // requirements.
        //
        printf("Out of heap memory");
        while(1) {;}
    }
}
//-----------------------------------------------------------------------------
// Return the number of outstanding heap allocations.
//
int DataCount(void)
{
    return mDataCount;
}
Back to article