AN Simple OOP in C
AN Simple OOP in C
Simple Object-Oriented
Programming in C
Document Revision D
June 2015
Table of Contents
1 Introduction..................................................................................................................................................... 1
2 Encapsulation.................................................................................................................................................. 1
3 Inheritance....................................................................................................................................................... 4
4 Polymorphism (Virtual Functions)................................................................................................................. 6
4.1 Virtual Table (vtbl) and Virtual Pointer (vptr)............................................................................................. 7
4.2 Setting the vptr in the Constructor............................................................................................................. 8
4.3 Inheriting the vtbl and Overriding the vptr in the Subclasses.................................................................... 9
4.4 Virtual Call (Late Binding)......................................................................................................................... 10
4.5 Examples of Using Virtual Functions........................................................................................................ 11
5 Summary
................................................................................................................................................... 11
6 References....................................................................................................................................................... 12
7 Contact Information........................................................................................................................................ 13
Legal Disclaimers
Information in this document is believed to be accurate and reliable. However, Quantum Leaps does not give any
representations or warranties, expressed or implied, as to the accuracy or completeness of such information and shall have
no liability for the consequences of use of such information.
Quantum Leaps reserves the right to make changes to information published in this document, including without limitation
specifications and product descriptions, at any time and without notice. This document supersedes and replaces all
information supplied prior to the publication hereof.
Introduction
Object-oriented programming (OOP) is not the use of a particular language or a tool. It is rather a way of
design based on the three fundamental design meta-patterns:
Encapsulation the ability to package data and functions together into classes
Inheritance the ability to define new classes based on existing classes in order to obtain reuse and
code organization
Polymorphism the ability to substitute objects of matching interfaces for one another at run-time
Although these meta-patterns have been traditionally associated with object-oriented languages, such as
Smalltalk, C++, or Java, you can implement them in almost any programming language including portable
ANSI-C [1,2,3,4,5,6].
If you develop end-user programs in C, but you also want to do OOP, you probably should be using
C++ instead of C. Compared to C++, OOP in C can be cumbersome and error-prone, and rarely
offers any performance advantage.
However, if you build or use application frameworks, such as the the QP/C and QP-nano active
object frameworks, the OOP concepts are very useful as the primary mechanisms of customizing,
specializing, and extending the frameworks into applications. In that case, most difficulties of doing
OOP in C can be confined to the framework and can be effectively hidden from the application
developers. This Application Note has this primary use case in mind.
This Application Note describes how OOP is implemented in the QP/C and QP-nano active object (actor)
frameworks. As a user of these frameworks, you need to understand the techniques, because you will
need to apply them also to your own application-level code. But these techniques are not limited only to
developing QP/C or QP-nano applications and are applicable generally to any C program.
Encapsulation
Encapsulation is the ability to package data with functions into classes. This concept should actually
come as very familiar to any C programmer because its quite often used even in the traditional C. For
example, in the Standard C runtime library, the family of functions that includes fopen(), fclose(),
fread(), and fwrite() operates on objects of type FILE. The FILE structure is thus encapsulated
because client programmers have no need to access the internal attributes of the FILE struct and
instead the whole interface to files consists only of the aforementioned functions. You can think of the
FILE structure and the associated C-functions that operate on it as the FILE class. The following bullet
items summarize how the C runtime library implements the FILE class:
1. Attributes of the class are defined with a C struct (the FILE struct).
2. Operations of the class are defined as C functions. Each function takes a pointer to the attribute
structure (FILE *) as an argument. Class operations typically follow a common naming convention
(e.g., all FILE class methods start with prefix f).
3. Special functions initialize and clean up the attribute structure (fopen() and fclose()). These
functions play the roles of class constructor and destructor, respectively.
You can very easily apply these design principles to come up with your own classes. For example,
suppose you have an application that employs two-dimensional geometric shapes (perhaps to be
rendered on an embedded graphic LCD). The basic Shape class in C can be declared as follows:
1 of 13
Application Note
Simple OOP in C
state-machine.com
Listing 1 Declaration of the Shape class in C
/* Shape's attributes... */
typedef struct {
int16_t x; /* x-coordinate of Shape's position */
int16_t y; /* y-coordinate of Shape's position */
} Shape;
/* Shape's operations (Shape's interface)... */
void Shape_ctor(Shape * const me, int16_t x, int16_t y,
Color outline, Color fill);
uint32_t Shape_moveBy(Shape * const me, int16_t dx, int16_t dy);
The Shape class declaration goes typically into a header file (e.g., shape.h), although sometimes you
might choose to put the declaration into a file scope (.c file).
One nice aspect of classes is that they can be drawn in diagrams, which show the class name, attributes,
operations, and relationships among classes. The following figure shows the UML class diagram of the
Shape class:
Shape
Name
compartment
x : int16_t
y: int16_t
Attribute
compartment
ctor(x, y)
moveBy(dx, dy)
Operation
compartment
2 of 13
Application Note
Simple OOP in C
state-machine.com
You can create any number of Shape objects as instances of the Shape attributes struct. You need to
initialize each instance with the constructor Shape_ctor(). You manipulate the Shapes only through the
provided operations, which take the pointer me as the first argument.
NOTE: The me pointer in C corresponds directly to the implicit this pointer in C++. The this
identifier is not used, because it is a keyword in C++ and such a program wouldn't compile with a C+
+ compiler.
3 of 13
Application Note
Simple OOP in C
state-machine.com
Inheritance
Inheritance is the ability to define new classes based on existing classes in order to reuse and organize
code. You can easily implement single inheritance in C by literally embedding the inhertited class attribute
structure as the first member of the derived class attribute structure.
For example, instead of creating a Rectangle class from scratch, you can inherit most whats common
from the already existing Shape class and add only whats different for rectangles. Heres how you
declare the Rectangle class:
Shape
x : int16_t
y: int16_t
(a)
Low memory
(b)
me
ctor(x, y)
moveBy(dx, dy)
super
Shape
Rectangle
Rectangle
inherited
width : uint16_t
height: uint16_t
ctor(x, y, width, height)
High memory
4 of 13
Application Note
Simple OOP in C
state-machine.com
NOTE: The alignment of the Rectangle structure and the inherited attributes from the Shape
structure is guaranteed by the C Standard WG14/N1124. Section 6.7.2.1.13 of this Standard, says:
A pointer to a structure object, suitably converted, points to its initial member. There may be
unnamed padding within a structure object, but not at its beginning.
With this arrangement, you can always safely pass a pointer to Rectangle to any C function that expects
a pointer to Shape. Specifically, all functions from the Shape class (called the superclass) are
automatically available to the Rectangle class (called the subclass). So, not only all attributes, but also
all functions from the subclass are inherited by all subclasses.
NOTE: There are no additional costs to using the inherited functions for instances of the
subclasses. In other words, the overhead of calling a function for an object of a subclass is exactly as
expensive as calling the same function for an object of the superclass. This overhead is also very
similar (identical really) as in C++.
5 of 13
Application Note
Simple OOP in C
state-machine.com
Figure 3 Adding virtual functions area() and draw() to the Shape class and its subclasses
abstract
Shape
x : int16_t
y : int16_t
ctor(x, y)
moveBy(dx, dy)
area() : uint32_t
draw()
Rectangle
virtual
functions
Circle
height : uint16_t
width : uint16_t
radius : uint16_t
ctor(x, y, w, h)
area() : uint32_t
draw()
ctor(x, y, r)
area() : uint32_t
draw()
6 of 13
Application Note
Simple OOP in C
state-machine.com
4.1
Listing 7 Virtual Table for the Shape Class (see also Figure 3)
typedef struct {
uint32_t (*area)(Shape const *me);
void (*draw)(Shape const *me);
} ShapeVtbl;
Virtual Pointer (vptr) is a pointer to the Virtual Table of the class. This pointer must be present in every
instance (object) of the class, and so it must go into the attribute structure of the class. For example, here
is the attribute structure of the Shape class augmented with the vptr member added at the top:
7 of 13
Application Note
Simple OOP in C
state-machine.com
4.2
Listing 9 Defining the Virtual Table and Initializing the Virtual Pointer (vptr) in the constructor
/* Shape class implementations of its virtual functions... */
static uint32_t Shape_area_(Shape * const me);
static void Shape_draw_(Shape * const me);
/* constructor */
void Shape_ctor(Shape * const me, int16_t x, int16_t y) {
static ShapeVtbl const vtbl = { /* vtbl of the Shape class */
&Shape_area_,
&Shape_draw_
};
me->vptr = &vtbl; /* "hook" the vptr to the vtbl */
me->x = x;
me->y = y;
}
As you can see the vtbl is defined as both static and const, because you need only one instance of
vtbl per class and also the vtbl should go into ROM (in embedded systems).
The vtbl is initialized with pointer to functions that implement the corresponding operations. In this case,
the implementations are Shape_area_() and Shape_draw_() (please note the trailing underscores).
If a class cannot provide a reasonable implementation of some of its virtual functions (because this is an
abstract class, as Shape is), the implementations should assert internally. This way, you would know at
least at run-time, that an unimplemented (purely virtual) function has been called:
8 of 13
Application Note
Simple OOP in C
state-machine.com
4.3
Listing 11 Overriding the vtbl and vptr in the subclass Rectangle of the Shape superclass
/* Rectangle's class implementations of its virtual functions... */
static uint32_t Rectangle_area_(Shape * const me);
static void Rectangle_draw_(Shape * const me);
/* constructor */
void Rectangle_ctor(Rectangle * const me, int16_t x, int16_t y,
uint16_t width, uint16_t height)
{
static ShapeVtbl const vtbl = { /* vtbl of the Rectangle class */
&Rectangle_area_,
&Rectangle_draw_
};
Shape_ctor(&me->super, x, y); /* call the superclass' ctor */
me->super.vptr = &vtbl; /* override the vptr */
me->width = width;
me->height = height;
Please note that the superclass' constructor (Shape_ctor()) is called first to initialize the me->super
member inherited from Shape. This constructor sets the vptr to point to the Shape's vtbl. However, the
vptr is overridden in the next statement, where it is assigned to the Rectangle's vtbl.
Please also note the the subclass' implementations of the virtual functions must match the signatures
defined in the superclass exactly in order to fit into the vtbl. For example, the implementation
Rectangle_area_() takes the pointer me of class Shape*, instead of its own class Rectangle*. The
actual implementation from the subclass must then perform an explicit downcast of the me pointer, as
illustrated below:
9 of 13
Application Note
Simple OOP in C
state-machine.com
4.4
RAM
Rectangle object
vptr
x
y
ROM
Rectangle vtbl
&Rectangle_area_
&Rectangle_draw_
width
Code (ROM)
uint32_t Rectangle_area_(Shape *me) {
return me->widtht * me->height;
}
void Rectangle_draw_(Shape *me) {
...
}
height
Circle object
vptr
x
y
Circle vtbl
&Circle_area_
&Circle_draw_
10 of 13
Application Note
Simple OOP in C
state-machine.com
4.5
Summary
OOP is a design method rather than the use of a particular language or a tool. This Application Note
described how to implement the concepts of encapsulation, (single) inheritance, and polymorphism in
portable ANSI-C. The first two of these concepts (classes and inheritance) turned out to be quite simple to
implement without adding any extra costs or overheads.
Polymorphism turned out to be quite involved, and if you intend to use it extensively, you would be
probably better off by switching to C++. However, if you build or use application frameworks, such as the
the QP/C and QP-nano active object (actor) frameworks, the complexities of the OOP in C can be
confined to the framework and can be effectively hidden from the application developers.
11 of 13
Application Note
Simple OOP in C
state-machine.com
References
[1]
[2]
Miro Samek, Practical Statecharts in C/C++, CMP Books 2002, ISBN 978-1578201105
[3]
Miro Samek, Practical UML Statecharts in C/C++, 2nd Edition, Newnes 2008, ISBN 9780750687065
[4]
[5]
[6]
Dan Saks, Implementing a derived class vtbl in C, Programming Pointers column February,
2013, Embedded.com.
[7]
Stanley Lippman, Inside the C++ Object Model, Addison Wesley 1996, ISBN 0-201-83454-5
[8]
[9]
[10]
12 of 13
Application Note
Simple OOP in C
state-machine.com
Contact Information
Quantum Leaps, LLC
103 Cobble Ridge Drive
Chapel Hill, NC 27516
USA
+1 919 360-5668
+1 919 869-2998 (FAX)
Practical UML
Statecharts in C/C++,
Second Edition: Event
Driven Programming for
Embedded Systems,
by Miro Samek,
Newnes, 2008
Email: [email protected]
Web : http://www.state-machine.com/
13 of 13