Is C++ the not-ready-for-prime-time embedded language?
A great debate is raging between the C and C++ camps over the near-term future of the
programming world. On the one hand, the C++ carrot dangling before us promises to
revolutionize the software industry with what one compiler vendor (Productivity Products
International, Inc.) has trademarked as Software-ICs, complete with databooks for picking
and choosing bug-free classes. In addition, proponents claim that programmers will need
fewer skills and that application development times will drop at a geometric rate instead
of the present arithmetic rate. They see companies creating libraries of Software-ICs to
support specialized needs just as PC-board layout houses, board manufacturers and parts
distributors produce and sell the physical components for manufacturing computer-based
products. It sounds great, but is the embedded world ready for C++? If not, what else can
we do now to improve program generation in the embedded world?
So many good books exist on C++ that I'm not going to delve into the specifics of the
language or it's methodology. If you want detailed information, Ref 1 is an excellent
source. In a nutshell, C++ characterizes a different way of approaching programming. To
begin with, instead of assuming that you'll write almost all the code for an application
from scratch, C++ promises to help you take advantage what you and other developers have
previously written by creating reusable classes of software objects. In this sense the
language attempts to model how humans learn. For example, if you give a racquetball to a
toddler, he calls it a ball. If you give him a basketball, he also calls it a ball because
both objects are round, bounce, and he can throw them. The toddler has generalized his
world into an easy-to-understand extensible framework. Give this child any object
ball-ness criteria and it's a ball until proven otherwise.
C++ allows the programmers to describe not only the actions that objects perform the
data but also the objects characteristics in terms of the data they contain. In
other words, the data items that define a ball's size and color are private to that
object, and other functions can't change them. For example, a racquetball is a particular
color and size, and only the ball's manufacturer can alter those characteristics. In the
same way, when you create an object in C++, the data defining its characteristics become a
part of it and C++ saves any changes to that description in the object's private data.
This feature of hiding data from other objects and functions is one key to C++'s power.
In order to change its embedded data, an object must possess a member function to
manipulate the data. As a case in point, consider the racquetball example mentioned above.
One characteristic of racquetballs is that the more times they bounce the less resilient
or bouncy they become. To reproduce this effect in a C++ object, the racquetball object
would use a predefined algorithm to reduce the ball's springiness each time one of the
ball's member functions bounces it. Hence the bounce-height function is localized to one
particular object and can only affect that object's data. Imagine how much more
complicated this process would be if you spread the control for the bouncing around in
fifty different places throughout your code!
A second feature of C++ is that it categorizes objects into classes that describe the
class member's common features. Every object belongs to a class, but no classes are
objects. In the previous example, the racquetball object belongs to a class called Ball.
You can define a new Ball object--called "blurp"--that you can use in the same
place as any other Ball object. If your program has a section for simulation the bouncing
of a ball, it can bounce a blurp as well as any other Ball object because the mechanism
that defines how to bounce a ball in incorporated into the class Ball. The class concept,
therefore, means you don't need to program each ball-bouncing function separately for each
type of ball.
Another feature of C++ (and all other object-oriented languages) is inheritance. To
understand this property of the language consider this question: What's a football? It's a
ball with slightly different characteristics. To represent this relationship in C++ you
create a class called Football that inherits from the Ball class. All previous
characteristics of a Ball are now part of Football, but the new Football class also has
some characteristics that set it apart from the Ball class. Hence, instead of coding a new
Football class from scratch, you inherit what you can and simply add what's different.
Although there's much more to C++ than I can cover in this brief space, the bottom line
is that this language attempts to force you into taking a more global view of the problem
space, not just for the particular problem but also for as yet undefined future problems.
The ultimate goal for all of C++ features is two-fold. First, you should become a more
productive programmer because you produce fewer lines of code to complete a project.
Second, the industry can produce high-quality software with less-skilled (and lower-paid)
programmers. After all, how much skill does it take to pick an appropriate Software-IC
from a databook?
Is it worth the effort?
Unfortunately, for embedded systems the jury is far from being in as to whether
converting to C++ is worth the effort. No matter what advertisements might say, that
language suffers form limitations that significantly impact its applicability. As anyone
who's programmed an embedded system knows, the two scarcest resources are memory and CPU
power. C++ uses significant amounts of both. Embedded systems typically have a limited
address space compared to PCs and so never seem to have enough memory for the delivered
application. Most programmers have spent many hours trying to shoehorn more program into
available space. C++ significantly exacerbates this problem.
As an example, I compiled and linked the ubiquitous "hello world" program
with a C compiler for my PC. The resulting executable was 3k bytes in size. I then
compiled and linked the same program with Ver 2 of the Borland Turbo C++ compiler.
Although the only difference between the two programs was that I substituted C++'s
"puts" ( << ) operator for the stdio puts() function, the resulting C++
executable was now 18K bytes! The C++ compiler manufacturer argued that the program was so
much larger that the C version because the linker had to include all the class hierarchies
for resolving the classes built from a base class called Object. They likened this size
penalty to the increase you realize from linking in an entire floating-point math library
to satisfy one floating-point operation. Further, that firm assured me that in larger
applications C++ programs come out ahead because most of the class support functions are
already linked in.
To test this point I next built a serial-communications application under both C and
C++. The C version required 18K bytes while the C++ application required 90K bytes! Memory
nowadays is much cheaper than before, but can an embedded system afford the extra cost?
Even if you can afford the memory chips,, does the target processor have the address space
necessary to access them? Finally you must consider penalties in compiling, linking and
executing larger programs.
Another major problem I've discovered in my search to change my office over to C++ is
the lack of compilers for most embedded processors. Sure, if you embed an 80X86 processor
or 68XXX you can find C++ compilers to support these chips. But what happens if you chose
and uncommon, single-source, chip like the INTEL 80C196KC? Because you can barely find a
working C compiler for this chip, I'd be deathly afraid of ever trying a C++ compiler for
it. Also, the size of the market always drives the level of tool support. Because C++
compilers and linkers are more complex to write than similar products for C, compiler
vendors will either choose not to support the language due to the development costs or
release a product the final cost of which is so high that it excludes much of the market.
Don't lose hope
Luckily, alternatives exist that can bypass many of these problems. For example, if you
want to use the language for a processor that doesn't have a native C++ compiler, try
using a preprocessor that translates a C++ program into standard C code. Although the
resulting program will be larger and slower than an equivalent C program, a more important
issue is how to debug the code. Specifically, how will a source-level debugger work with C
code that you didn't write and that you don't want to patch? You could easily end up
spending one hour a day debugging and the other seven trying to figure out what the
preprocessor did to the code.
A better alternative might be to simply forget about C++ and object-oriented design for
the present: these tools might be the future but they aren't right for the embedded world
today. Instead, remember that part of what C++ tries to do is enforce a design methodology
that good programmers follow anyway. By developing skill with current tools you can gain
most, if not all, of C++'s advantages without any of its limitations. The secret is
knowing how to use what's available. Rely on your intelligence, instead of a tool, for
creating productive reusable code. To further you in that effort, my next column will show
how to use present compilers to build well-designed systems that survive repeated changes
and produce code that's usable in other designs--all without giving up the compactness,
speed, and flexibility of C. PE&IN
Reference: Stroustrup, B, The C++ Programming Language, Second Edition, Addison-Wesley
Publishing Co, Reading, MA, 1991, ISBN 0-201-53992-6.