Web Images Videos Maps News Shopping Gmail more »
Recently Visited Groups | Help | Sign in
Google Groups Home
Message from discussion Embedded C or C++
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Jonathan Kirwan  
View profile  
 More options Mar 18 2002, 5:05 am
Newsgroups: comp.arch.embedded
From: Jonathan Kirwan <jkir...@easystreet.com>
Date: Mon, 18 Mar 2002 10:05:31 GMT
Local: Mon, Mar 18 2002 5:05 am
Subject: Embedded C or C++
Intro
-----

A thread has been cross-posted on four groups on the subject of C or
C++.  As I'm only writing as an embedded programmer, I wanted to avoid
adding to that cross-posting fray.  I'm posting this only in CAE.

I should say what my biases are before I speak.  I'm not a C expert,
though I've been using C since 1978 and have written a working, toy C
compiler.  I'm not a C++ expert, though I've been using C++ since 1989
for Windows programming.

What I care about is very different from what many C++ programmers
care about, because my area of designing and coding small embedded
systems does NOT include any Windows or Linux programming.  (That is
not to say I don't program on Windows or Linux, just that I don't
program them for embedded products.)  And keep in mind that I'm
talking about only one facet of my programming experience -- the
embedded facet.  And that my embedded universe is NOT the same as the
embedded universe as a whole -- again, it is only a subset of that,
too.

I'm very interested in using C++ in my projects.  I'm just quite
skeptical, for a variety of reasons, about using one.

My World and Welcome To It
--------------------------

My world includes processors with as little as 500 instructions of
code space, or so.  Some of my applications only require 1/10th of
that, to be honest.  I rarely, very rarely get to use a processor with
more than 32k instructions available.  The RAM is often limited to a
few tens of bytes and rarely gets more than 2-3k byte.  I'm currently
working on several projects:  one with 512 words of code space and 128
bytes of ram; one with 16k word of code space and 1500 bytes of ram;
and one with 1/2Mb of code space and 1/8Mb of ram.  This isn't an
unusual mix or me.

For the most part, my applications are sensitive to cost, power, board
space, radiated EMF, and so on.  In part, that means in some cases
that an additional $20 in the CPU section makes the difference between
having a product which succeeds and having a product no one buys, even
if everything else is just fine.

Now for the smaller CPU environments, I usually use assembly, because
those applications often have extremely tight requirements.  I may
need to count cycles to make certain that either path across a branch
boundary takes the same cycle count, for example.  Further, the
application design is the big part of it and the coding time is rather
small by comparison.  So the issue of "faster coding" doesn't really
come into place as much on these smaller systems.  For the larger
applications, I often use C, mixed with assembly.

I can't afford templates, if instantiating support for a data type
causes functions I don't actually use to be generated and linked, for
example.  I can't afford exception handling, if the compiler still
generates defensive code even when I don't use any syntax in my code
for them.

For the greater part, well-optimized and well-designed libraries buy
me exactly zilch for my applications.  That's not entirely true, of
course.  Floating point libraries, for example, can be quite useful.
But by and large, libraries either aren't re-entrant; they make
assumptions which aren't true in my environment; they provide far too
many features and I have no choice but to include them all or use none
of them; and so on.  So talking about the benefits of C++ libraries,
templated or otherwise, makes little headway with me.  At least,
that's how it has been so far for me.

I don't have a purchased operating system, I write my own.  I don't
have a supplied library, I write my own.  I do care about ram usage,
stack usage, and program space.  I care a great deal about partial
template specialization in C++, but I wouldn't care much about access
to large, standard template libraries.

I care a great deal about having control over the processor I work on.
I care most of all in knowing exactly what kind of code is generated
by the compiler I use for syntax I write.  I need to have an intuitive
understanding of what happens for each and every line of source code I
lay down, when I'm writing it.

How many people using C++ know exactly how the vtable mechanism works?
In the face of multiple inheritance?  With virtual base objects?  How
many people how a C++ compiler supports dynamic casts or know what
causes the C++ compiler to generate (or not generate) support for it?
What mechanism does a C++ compiler use for exception handling?  What
does it cost?  Where?  If one stays away from a mechanism, how much of
it is still present?

I need to know what is generated, time and again.  In short, my focus
is exactly on the compiler itself and my ability to know what it does.
For folks working on large systems, it's enough for them to know that
it "doesn't cost much."  For me, cost isn't just a little bit of code
space, a little bit of ram space, and a little bit of execution time.
Sometimes, it's not a matter of degree, but a matter of YES/NO,
GOOD/BAD, WORKS/DOESN'T WORK.  I can't leave these details as some
abstract matter of implementation I can ignore.  Often, the compiler
makes decisions about "what's works for non-critical situations"
because that is where many of the applications are at.

But that's not where MY applications are at.  All the details must be
well understood.  The compiler itself and the linker support is my
main focus.  Not the libraries.

Hopefully, that gives an idea of where I'm coming from to those who
use C++ as part of their application development on systems where the
equivalent transistor count is measured in the 100's of millions or
billions.  I just don't live in their world.

So I'm speaking as one who uses assembly and C for their current
embedded applications and who would enjoy using a subset of C++.  But
I also have some concerns, too.

C++ as a Superset of C
----------------------

Strictly speaking, C++ is not a superset of C.  If unsure of that
fact, pages 816 to 818 in Stroustrup's 3rd edition of "The C++
Programming Language" indicates that some ANSI C programs are not
valid C++ and includes examples, as well.  But for many practical
purposes, developing new source code for C++ looks about like working
with a superset of C.

But it is the object code generation that is more the question mark
for an embedded programmer.  Not so much the source code input to the
compiler.

C++ Exception Semantics
-----------------------

For embedded use, this is an area which worries me where separate
compilation is supported (which it is, in every case I am aware of.)

I know.  Most folks just say, "Well, don't use exceptions then."  But
a properly functioning C++ compiler, it seems to me, must generate
correct code for source code unit A when it has absolutely no idea
what kind of exception handling may be required in source code unit B,
compiled separately and perhaps at a very different time.  And I have
absolutely no confidence at all that the linkers available to an
embedded programmer will cover over the problem I'm about to
illustrate here.

Take this sequence of code, found as part of some function in some
source code file:
       .
       .
       foo ();
       String s;
       foo ();
       .
       .

For purposes of discussion, assume this fragment occurs as part of a
routine that doesn't use try..catch at all in a source code module
that doesn't use try...catch or declare any throws, either.  In fact,
assume the module was written almost like C code by someone who wasn't
thinking about or worrying over exceptions at all.  It might even be a
C source code file modified slightly to take advantage of a few C++
features, such as the String class.

Also, for purposes here, assume that foo() is an external procedure
and that the compiler has a declaration for it, but does not know its
definition.

The C++ compiler, being a smart and proper C++ compiler, sees the
first call to foo() and can just allow a normal stack unwind to occur
if foo() throws an exception.  In other words, no extra code is needed
at this point to support the unwind process.  But once String s has
been created, the C++ compiler knows that it must be properly
destroyed before an unwind can be allowed if an exception occurs later
on.  So the second call to foo() is semantically different from the
first.  If the 2nd call to foo() throws an exception (which it may or
may not) in foo(), the compiler must have placed code designed to
handle the destruction of String s before letting the usual unwind
occur.

Now, the above is a case where a common, garden variety embedded
programmer would imagine that the two calls to foo() would take the
same time, require the same resources, and otherwise be the same.
Most programmers I know would believe and defend that position, even
when told that instead of a C compiler it is a C++ compiler looking at
the source code.  Even the more sensitive thinking programmers who are
aware of exception handling, stack unwinding, and so on, would tend to
pause for a while when faced with a source file which quite simply
contains NO try..catch blocks, no throws, and otherwise completely
avoids any use of C++ syntax related to exceptions.

Yet the C++ compiler *must* be aware of the difficulties and *must*
generate different code in these cases, where foo() is permitted to
generate exceptions.  And without an empty throw clause in it's
declaration, the C++ compiler must assume that it may.

All this, in a snip of code in a function in a module none of which
has any syntax dealing with exceptions at all.

Now, I know it is possible to declare foo() with a throw in the
declaration so that the compiler knows it cannot throw an exception.
The compiler may be able to do better, in code generation in such a
case where careful, explicit attention has been given to the function
declarations.  But in reality, in embedded systems trying to make th
transition to using C++ with programmers learning to use C++ features
and perhaps modifying existing code to be compiled with a C++
compiler, this is an unreasonable expectation.  Many programmers are
simply going to want to recompile their existing application with the
new C++ compiler and then start extending.  Or, if making a new
application, take a while to learn all the ins and outs of using C++
as it applies to embedded programming.

In an embedded system, things like this will kill an attempt to make
the transition to C++.  And tracking them down, looking in the source
code, won't help as much as you'd wish.  C++ obfuscates much in the
resulting code and it takes a while to grow accustomed to it.

This is the kind of thing which makes using C++ in small embedded
system design so down-right dangerous, even when C remains innocuous.

...

This simple bit of C++ code generates interesting output, with speed
optimizations enabled on VC++ 5.0, for example:

  struct T { ~T() { /* assume some non-trivial code here */ } };
  extern void foreign ();
  void example ();
  void example () { T s; foreign (); }

This specifies a destructor for T which must be called in case of
exceptions.  example() calls a foreign function which may generate
exceptions.  Since the compiler of this code has no way to be certain
that foreign() cannot throw an exception, it must assume that it
might.  Accordingly, the compiler needs to generate the necessary code
to handle the destruction call to obliterate the object T if such an
exception does occur in foreign().

In other words, exception handling quite often comes at a price, even
in functions that the programmer knows cannot generate exceptions.
Sometimes, that price isn't all that clear to the programmer.  One can
argue it should be, but the fact is that it isn't so obvious in
practice, when writing each line of code.  And whether the price is
worth paying, assuming you know the price, depends on the application.

...

To add more fuel to the fire, unlike C's malloc, C++'s new is supposed
to use exceptions to signal when it cannot perform raw memory
allocation.  In addition, so will <dynamic_cast>.  (Again, see
Stroustrup's 3rd ed., The C++ Programming Language, pages 384 and 385
for the standard exceptions in C++.)  Many compilers allow this
"proper behavior" to be disabled, of course.  But if you stay true to
C++ form, you will incur some overhead due to properly formed
exception handling prologues and epilogues in the generated code, even
when the exceptions actually do not take place and even when the
function being compiled doesn't actually have any exception handling
blocks in it.  Stroustrup has publically lamented this reality.

...

Exception handling killed the CFRONT 4.0 project.  It unfortunately
does have a code size and runtime price, even when recompiling simple
C code which is otherwise source compatible.  For embedded projects on
small systems, these kinds of worries present real issues to anyone
making a language choice between C and C++.   And more, the skill and
experience required for those programming embedded programs in C++ is
indeed higher.  They must be aware of these subleties.  These are
things most C++ programmers simply aren't well aware of, while they
are coding away.

Templates
---------

Templates are fantastic!  I want them.  Careful use can be entirely
harmless and offer good benefits for embedded programming.  But the
more you like them, the more you are tempted to use them.  And without
partial template specialization, the result can spell disaster for
embedded programming.  Without it, code bloom is a serious risk which
could kill a small-memory embedded project in a flash.

Care can be taken, of course.  But it's just one more issue among many
to watch out for.  And frankly, the application itself poses enough on
its own not to have nasty surprises waiting in store, by the
compiler's own hand.

Other Issues
------------

* When a C++ function returns an object an unnamed compiler temporary
is created and destroyed.  Some C++ compilers can provide efficient
code if an object constructor is used in the return statement, instead
of a local object, reducing the construction and destruction needs by
one object.  But not every compiler does this and many C++ programmers
aren't even aware of this "return value optimization."  I know that
they should be and that when you use C++ features, you need to know
what they cost you.  But it remains one of those common risks which C
exposes (because it doesn't support these semantics directly) and
which C++ can too easily hide.

* Providing an object constructor with a single parameter type may
permit the C++ compiler to find a conversion path between two types in
completely unexpected ways to the programmer.  This kind of "smart"
behavior isn't part of C.

* A catch clause specifying a base type will "slice" a thrown derived
object, because the thrown object is copied using the catch clause's
"static type" and not the object's "dynamic type."  A not uncommon
source of exception misery (when you can afford exceptions in your
embedded code.)

* C++ compilers can automatically generate constructors, destructors,
copy constructors, and assignment operators for you, with unintended
results.  It takes time to gain facility with the details of this.
Yet more learning curve.

* Passing arrays of derived objects to a function accepting arrays of
base objects, rarely generate compiler warnings but almost always
yields incorrect behavior.

* Since C++ doesn't invoke the destructor of partially constructed
objects when an exception occurs in the object constructor, handling
exceptions in constructors usually mandates "smart pointers" in order
to guarantee that constructed fragments in the constructor are
properly destroyed if an exception does occur there.  (See Stroustrup,
page 367 and 368.)  This is a common issue in writing good classes in
C++, but of course avoided in C since C doesn't have the semantics of
construction and destruction built in.  Writing proper code to handle
the construction of subobjects within an object means writing code
that must cope with this unique semantic issue in C++; in other words
"writing around" C++ semantic behaviors.  There is often a relatively
small cost to doing this.

* C++ copies objects passed to object parameters.  For example, in the
following fragments, the call "rA(x);" may cause the C++ compiler to
invoke a constructor for the parameter p, in order to then call the
copy constructor to transfer object x to parameter p, then another
constructor for the return object (an unnamed temporary) of function
rA, which of course is copied from parameter p.  Worse, if class A has
its own objects which need construction, this can telescope
disasterously.  (A C programmer would avoid most of this garbage, hand
optimizing since C programmers don't have such handy syntax and have
to express all the details one at a time.)

    class A {...};
    A rA (A p) { return p; }
    // .....
    { A x; rA(x); }

* longjmp doesn't have a portable behavior in C++.  (Some C
programmers use this as a kind of "exception" mechanism.)  Some C++
compilers will actually attempt to set things up to clean up when the
longjmp is taken, but that behavior isn't portable in C++.  If the
compiler does clean up constructed objects, it's non-portable.  If the
compiler doesn't clean them up, then the objects aren't destructed if
the code leaves the scope of the constructed objects as a result of
the longjmp and the behavior is invalid.  (If use of longjmp in foo()
doesn't leave a scope, then the behavior may be fine.)  This isn't too
often used by C embedded programmers and they should make themselves
aware of these issues before using them.  But it's just one more
detail.

Benefits
--------

There are so many potential benefits for C++, as well.  I could go on
as I have already.  But that's not my focus here so I'll leave that
for others.  But I thought I'd acknowledge here something that has
already been pointed out elsewhere in the thread:

In order to allow the C library function, qsort(), to be used on a
variety of element types, a function pointer is passed to use for
comparing two elements.  The cost of using a function call can be
expensive, compared to the overall sort time.  C++ improves this
situation by supporting templates that the compiler can use to
automatically generate more efficient code in specific cases.

This is just one of many benefits of C++.  (Stroustrup talks about it
on page 430 and 431 of his 3rd edition, mentioned earlier.)

My main complaint is in developing the knowledge needed to understand
the implementation details, many of which are no longer explicit.  In
my kind of embedded programming, it's vital that everything is clear,
open, and "on the table" so to speak.  And C gives you that (mostly.)
It does what you say and little else.  C++ does much more and enough
of it is hard to see to cause some difficulties for an embedded
programmer with very limited resources.  Sometimes, one needs a
comprehensive view of the entire project in order to understand just a
small sample of code at hand.

Summary
-------

C++ offers fantastic possibilities for embedded programming.  But C++
is NOT a panacea and it is NOT always better than C, for each and
every embedded use.  And even in the case when it is used in competent
hands, it may not always be better.

Jon


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.

Create a group - Google Groups - Google Home - Terms of Service - Privacy Policy
©2009 Google