Embedded developers abandon C++ in droves. According to the 2007 survey published in the ESD magazine, the C++ use declined by one-third compared to year before, which was offset by an equal rise in popularity of C—the only viable alternative in embedded.
Even though the last year was most dramatic, the trend has been actually continuing for a number of years. This couldn’t go unnoticed by UML tool vendors, who desparately have been trying to cater to C programmers. For example, you can check out the DDJ article “UML for C Programmers” (which seems to be pretty exact re-print of the Embedded Systems Conference paper “UML for C-Based Embedded Systems“). To my surprise, nether this article, nor the ESC class mention any well-known techniques of mapping objects and classes to C. I’m sure that it is not what UML vendors like. After all, UML is crippled without objects. (The only real meat remaining are state machines.) But I suppose that the marketing departments of I-Logix/Telelogic have done their homework. Apparently embedded developers don’t like to hear about objects anymore.
I find this really disturbing. It seems that “object” and (pardon my language) “class” are becoming dirty words in the embedded circles. C++ decline is one thing. But abandoning objects is a different story. Aren’t we throwing out the baby with the bath water?
One would assume that the 21st-century software developers have objects in their bones and everyone knows how to program with objects in any language, including C. Apparently increasing number of us don’t know that object technology is a way of design, not the use of any particular language or tool. Most design and implementation techniques now associated with C++, Smalltalk, or Java, actually long predate these languages.
So here is how you implement a Point class in C (a Point that you can put on a screen):
typedef struct PointTag { int16_t x; /* x-coordinate */ int16_t y; /* y-coordinate */ } Point; void Point_ctor(Point *me, int16_t x, int16_t y) { me->x = x; me->y = y; } void Point_move(Point *me, int16_t dx, int16_t dy) { me->x += dx; me->y += dy; } int16_t Point_dist(Point const *me, Point const *other) { int16_t dx = me->x – other->x; int16_t dy = me->y – other->y; return (int16_t)sqrt(dx*dx + dy*dy); } . . . /* example of using Point objects */ Point foo, bar, tar; /* multiple instances of Point */ int16_t dist; Point_ctor(&foo, 0, 0); Point_ctor(&bar, 1, 1); Point_ctor(&tar, -1, 2); dist = Point_dist(&foo, &bar); Point_move(&tar, 2, 4); dist = Point_dist(&bar, &tar); . . .
You can create any number of Point objects as instances of the Point struct. You need to initialize each point with the “constructor” Point_ctor(). You manipulate the Points only through the provided functions, which take the pointer “me” as the first argument. The “me” pointer corresponds directly to the implicit “this” pointer in C++.
Moreover, you can as easily implement single inheritance. Assume for example, that you need to add a color attribute to Points. Instead of developing such a colored-Point from scratch, you can inherit most what’s common from Point and add only what’s different. Here’s how you do it:
typedef struct ColoredPointTag { Point super; /* derives from Point */ uint16_t color; /* 16-bit color */ } ColoredPoint; void ColoredPoint_ctor(ColoredPoint *me, int16_t x, int16_t y, uint16_t color) { Point_ctor(&me->super, x, y); /* call superclass’ ctor */ me->color = color; } ... /* example of using ColoredPoint objects */ ColoredPoint p1, p2;int16_t dist; ColoredPoint_ctor(&p1, 0, 2, RED); ColoredPoint_ctor(&p2, 0, 2, BLUE); /* re-use inherited function */ dist = Point_dist((Point *)&p1, (Point *)&p2);
As you can see, you implement inheritance by literally embedding the superclass (Point) as the first member of the subclass (ColoredPoint). Such nesting of structures always aligns the first data member ‘super’ at the beginning of every instance of the derived structure. This alignment is guaranteed by the C standard. Specifically, WG14/N1124 Section 6.7.2.1.13 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”. This alignment lets you treat a pointer to the derived ColoredPoint struct as a pointer to the Point base struct. All this is legal, portable, and blessed by the Standard.
With this arrangement, you can always safely pass a pointer to ColoredPoint to any C function that expects a pointer to Point. Consequently, all functions designed for the Point structure are automatically available to the ColoredPoint structure. They are all inherited.
There is really nothing to it.
8 Responses
There are a couple of things here I feel worthy of comment. First, beware taking survey results and especially their year-over-year changes at face value. As a former editor of that very magazine I can tell you that there is a selection bias. Sure, they ask the same questions every year and via the same sampling process. But at each opportunity to add, replace, or pare readership, the folks who sell the ads in these free magazines typically select only those readers they think are most coveted by advertisers. This favors 32-bit over 8-bit, big teams over small, and even certain regions of the country.To be specific, as print circulation of ESD has declined in recent years these surveys have been distributed to a sample of readers of sister pub EETimes, which is much more likely to be read by EEs inclined toward electronics design.The second comment I have is that objects are not relevant to much of what embedded software developers do. (State machines are very relevant, of course.) But the only “objects” to be found are typically single instance peripherals. Lacking the need for class inheritance or dynamic allocation of object instances, embedded systems stand primarily to benefit from encapsulation.
Mike, thanks for your comment! I agree with you that all survey results must be taken with a grain of salt. And certainly, shifts in the respondents’ base to more hardware-oriented firmware developers could explain a lot of the seeming decline of C++.
Even so, however, I believe that the general trend away from objects in embedded is real. And I claim it mostly because I see that tool vendors, such as I-Logix/Telelogic, pursue “C” programmers more aggressively than ever, expending significant resources in the process.As to the irrelevancy of objects in embedded software development, I obviously disagree. Object technology is the biggest advancement in software development since structured programming, as attested by thousands of books, countless articles, special OO conferences, and explosion of OO programming languages.
Granted, embedded software development is different than programming for the desktop, but it’s not that different to completely render irrelevant the most valuable lessons people learned about software structure and code reuse.And sure, objects are everywhere in embedded designs. Tasks, events, state machines, timers, queues, data packets, are all objects. There is of course a lot of inheritance going on as well.
Take for example the popular Micro-C/OS RTOS that you’ve been teaching in Florida last week. One of the central elements of this RTOS’ design is the OS_EVENT data type, which represents an operating system event. OS_EVENT is a class with a constructor (OSEvent WaitListInit()), and member functions (OSEventTaskRdy(), OSEventTaskWait(), and OSEventTaskTO()). However, OS_EVENT is actually an abstract class that is only intended for inheritance and cannot be instantiated directly. OS_EVENT class is inherited by semaphores, message mailboxes, message queues, event flags, and so on, which all are classes with constructors, destructors, and member functions. All subclasses of OS_EVENT reuse the member functions from the superclass, which leads to substantial code reuse and better code structure.My point here is that good embedded designs use objects, whether they realize it or not. But of course, it’s easier if you do realize that you’re using objects because it spares you re-inventing the wheel, so that you can immediately benefit from all the body of knowledge about design principles, idioms, and patterns.
Hello Miro,
this is a very good paper! This is also the way Rhapsody generates C-code from UML models.
Although I didn’t use Rhapsody myself, I successfully used these ideas in an embedded system (a dosing pump). E.g. classes for the different operation modes are inherited from a base operation mode class. This helps to re-use code and improves decoupling of components.
Hi your web page url: https://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c appears to be redirecting to a completely different web-site when I click the home-page button. You may want to have this checked.
This inheritance trick is mind blowing. Thanks a lot!
A great little snippet of info, especially when it comes to explaining how inheritance works (and providing references to the C standard!).
I agree, one of the primary reasons for implementing embedded OO-design in C would be for encapsulation/modularisation. EVEN IF you don’t ever plan on using that module in another application, OO-design/encapsulation makes it easier to write code in a clear and maintainable manner.
Hi, great article, that helped me understand how that magical word “class” in C++ works. I’m just beginner in C/C++ and I am not sure about destroying created objects since they are stored on stack (of course I could store them on the heap if I want).
But let’s say that I have some objects, that throughout program “die” and I’d like to free memory for new ones. How do I do that?
I am practicing this on example with class NPC, where one NPC hits another and if some of NPCs run out of health, it dies and then I need to destroy that object. Is that possible with stack storage?
The memory management for the objects is really a separate concern from object-orientation. The objects (instances of the “class” struct) can be allocated whichever way you see fit. They can be on the stack, if you define them inside functions, such as main(), but more often they are allocated statically, if you define them outside of any function (typically at the top of a .c file).
It is also possible to allocate objects on the heap with the malloc() function call. In C, such objects need to be subsequently initialized with the explicit “constructor” call. (In C++, the operator new performs both memory allocation and constructor call). After you are done with an object, you need to explicitly call the “destructor” function and you need to call free() on the pointer to the object. If you don’t do this, you will create a memory leak. (Again, in C++, the operator delete performs both the destructor call and freeing of the memory).