C++ in embedded systems - Page 3

Do you have a question? Post it now! No Registration Necessary

Translate This Thread From English to

Threaded View
Re: C++ in embedded systems
Quoted text here. Click to load it

That was probably me. I avoid C++, but I embrace OO and code in C/assembler.

In response to other posts in this thread - it's not OO that's the problem,
it's *bad* OO that's the problem. To be specific, OO is really nothing more
than good decomposition. I tend to think of it as "structured data" - I
don't see it being any more/less difficult/controversial than structured
programming (and I remember when my colleagues thought that was a passing
fad too, and swore by their GOTOs). It's just a question of breaking data
and code down into "objects" in the sense of good modularity. Exactly how
doesn't matter, but bad decomposition is always bad - OO or not.

Re C++: my own (again controversial) view is that C++ is a *terrible*
implementation of a good idea and tends to lead to poor OO and poor code. My
criticisms of C++ include:
  - use of the heap (heaps have no place in embedded systems, since malloc
can fail at runtime)
  - operator overloading (renders code hard to read and frequently misleads)
  - poor hiding of private elements (since they're in the header)
  - the difficulty in reading code without having every single header file
open
  - too much emphasis on runtime (late binding, name mangling etc etc)
  - exception-handling (no substitute for good error handling)
  - bloat in general

Regardless of the buzzwords, *good* code means that every module has a
defined public interface that tells you everything you need to know abouit
that module. If it's well done, you shouldn't need to look under the hood -
just use the interface. My view (to repeat myself) is that software
engineers often fail in this - and OO makes this all the more painful, but
it's essentially poor design whichever way you look at it. (If you need an
example of bad OO, consider MFC.)

The bit that matters:
  - OO saves me time. It renders my projects clearer and more maintainable.
  - C++ does not seem to save programmers time. I've seen teams struggle
with comprehension before the first release, let alone later.
  - My own stated objectives are clarity and hence maintainability. I don't
really care too much how this is achieved, so long as it *is* achieved.

Steve
http://www.sfdesign.co.uk
http://www.fivetrees.com



Re: C++ in embedded systems

:   - use of the heap (heaps have no place in embedded systems, since malloc
: can fail at runtime)

You can do all allocation statically in C++ as you would do in C, too.
Nothing is forcing you to use the heap in C++.

Think of C++ as 'OO C, forgetting STL and templates' - execpt you have
inheritance and the possibility of creating interfaces
('pure virtual' functions) nor need to pass 'this' pointer around
explicitely.

Both decrease the error possibility and add safety by eliminating the
need for a switch-case -statement (compiler does it compile time).

Inheritance is a compile time mechanism in C++ and doesn't cost
anything runtime, a virtual function call is one memory access
more (a lookup to vtable).

:   - operator overloading (renders code hard to read and frequently misleads)

This is true. Operator overloading is very often used in a wrong way,
but somewhere it fits.

:   - exception-handling (no substitute for good error handling)

Isn't exception handling error handling also?

:   - bloat in general

Care to emphasise? Using class or function templates can cause this, but
if you implemented the same functionality not using templates, and
created a version of a template function for all data types you need,
you get the same amount of code execpt that the C++ compiler does it for
you.

Re: C++ in embedded systems

Quoted text here. Click to load it

IMO, the language itself is just way to big and complex. Almost
nobody understands it well enough to use it effectively and
safely.  Comapre the C++ language spec with the Modula-3 spec,
or the Scheme spec (through in CLOOPS even), or the Smalltalk
spec.  

--
Grant Edwards                   grante             Yow!  Darling, my ELBOW
                                  at               is FLYING over FRANKFURT,
We've slightly trimmed the long signature. Click to see the full one.
Re: C++ in embedded systems
snipped-for-privacy@visi.com (Grant Edwards) wrote in message

Quoted text here. Click to load it

Or even the Ada spec...  ;-)

Re: C++ in embedded systems
Quoted text here. Click to load it

Yup -- I've even got a copy of that somewhere...

--
Grant Edwards                   grante             Yow!  Where's th' DAFFY
                                  at               DUCK EXHIBIT??
We've slightly trimmed the long signature. Click to see the full one.
Re: C++ in embedded systems
Quoted text here. Click to load it

I don't think C++ (or *some features* of C++) should be ruled out in
embedded system design. Classes without virtual functions, single
inheritance, namespaces, const variables, visibility, stricter type system,
operator and function overload won't add any complexity to the generated
code but would make the source cleaner, more maintainable and would uncover
whole classes of bugs at compile time. Inline functions should even help the
compiler to do a better job in optimizing your code and also have the added
benefit of easy debugging - compared to macros. Virtual functions can be
good or bad depending on your application and design, but if you would use
function pointers frequently in your C code, probably that helps too.

I wouldn't use templates unless for fairly trivial scenarios (STL is out of
question). I would avoid exception handling by all costs.

One thing I have hardly seen mentioned before is the runtime support aspect:
many C++ features require runtime support to work correctly. Exception
handling is one example, static class initializers (depending on the
compiler) is another. It's also harder to program in C++ without a heap (new
and delete operators) since they can be called by compiler-generated code in
some cases, but can be done. In some cases you can arrange your building
system so that a compile-time error occurs if someone tries to call those
operators.

You can have serious problems if you use paging (due to address space
limitations) and inheritance together.

All in all, C++ is much more than just OO, and many features are really
useful for all types of projects. You have to judge each feature before you
use it though.

Regards,
Andras Tantos




Re: C++ in embedded systems
On Thu, 14 Aug 2003 09:27:55 -0700, "Andras Tantos"


Quoted text here. Click to load it

Aside from explicit calls in template expansions, under what
circumstances have you seen "new" or "delete" called from compiler
generated code?  

"New" and "delete" are specifically defined as having object dependent
functionality and are always to be called explicitly.  The compiler
understands as special cases that these operators can be applied to
basic types and aggregates (ie. non-objects), but it can't depend on
the operators having any particular functionality for any type.  

I would call the implicit use of new or delete by a compiler a VERY
serious bug.

George

Re: C++ in embedded systems
Quoted text here. Click to load it

I seem to recall seeing it in some exception-handling code. I might be
wrong. Anyway, the fact is that exception handling might need to construct
object off the stack for proper behavior. Some compilers might use a part of
the data-segment for that, others might use the heap. Here's an example
code:
=============================
#include <stdio.h>
#include <stdlib.h>

void *operator new(size_t aSize) {
 printf("new\n");
 return malloc(aSize);
}

class C {
public:
 C() { printf("C::C\t\t0x%08x\n",this); }
 C(const C &cc) { printf("C::C(C)\t\t0x%08x <- 0x%08x\n",this,&cc); }
 ~C() { printf("C::~C\t\t0x%08x\n",this); }
 virtual void Print() { printf("C::Prn\t\t0x%08x\n",this); }
};

int bad() {
 C c;
 throw c;
}

int good1() {
 printf("---------- good 1\n");
 try { bad(); }
 catch (C ex) {
  ex.Print();
 }
}

static C sc;
int main() {
 C *c = new C;
 try {
     good1();
 } catch (...) {
  printf("Unhandled...\n");
 }
 return 0;
}
==========================

I know I leak memory here, but that's not the point. Compiled it with GCC
3.2 for cygwin and got the followig output:

==========================
C::C            0x0040e020
new
C::C            0x0a040820
---------- good 1
C::C            0x0022fe60
C::C(C)         0x0a040880 <- 0x0022fe60
C::~C           0x0022fe60
C::C(C)         0x0022fea0 <- 0x0a040880
C::Prn          0x0022fea0
C::~C           0x0022fea0
C::~C           0x0a040880
C::~C           0x0040e020
==========================

Although new was called only once, the temporary object, constructed for
exception handling is clearly generated on the heap (this pointer is
0x0a040880).

If a compiler reserves part of the data segment to store the temporary
object, it has to make sure the space is large enough to hold any object it
needs to construct there. It's a non-trivial way of using up data-segment
space and can cause problems in an embedded system. If the compiler uses
heap as GCC seems to do so, it obviously can cause problems, especially
since it goes around new.

Quoted text here. Click to load it

Compilers tend to depend very much on the functionality of the runtime
functions. If you redefine those, you'd better make sure you implement them
in a conforming way. For example memcpy calls might be translated into
intrinsics. With your special memcpy implementation, you might not even be
sure your function gets called at each place you've written it down. I'm not
sure about GCC, but I'm pretty sure I've seen it somewhere.

Regards,
Andras



Re: C++ in embedded systems

Quoted text here. Click to load it

I disagree.  I think that in embedded work, it's import for the
programmer to have a thorough understanding the the language
and what the compile is going to do.  From what I've seen,
that's possible with C, Modula-[23], Ada, and various other
languages.  It doesn't seem to be possible with C++.  The
_language_ itself is too complex and baroque to understand
completely enough for it to be used effectively in some
embedded environments.

--
Grant Edwards                   grante             Yow!  I know how to get the
                                  at               hostesses released! Give
We've slightly trimmed the long signature. Click to see the full one.
Re: C++ in embedded systems
On 14 Aug 2003 17:34:12 GMT, snipped-for-privacy@visi.com (Grant Edwards)

Quoted text here. Click to load it

Agreed.  I've spent some time getting familiar with the code
generated by C+ compilers for various constructs.  Mostly, this
has been with compilers designed for the x86 used in PCs, but
it's not limited to that.  There's also a few articles and a
book or two around which discuss the implementation details,
too.

Templates instantiated for a data type often cause functions I
don't actually use to be generated __and__ linked, for example.
I know that if everything is done which should be done and the
linker and compiler work together flawlessly, it's possible to
correctly eliminate almost all of this.  But what chance is
there that an embedded compiler (other than GNU?) tool chain
will get the time put in, here?  Even in the case of x86
compilers, where there is a great deal of effort and time spent
in development, they fail to get this done well enough for many
smaller embedded systems.

For these smaller systems, too, partial template specialization
is almost a requirement.  Who provides that for embedded work?

So, don't use templates, you say??  Okay.  Then what about
exception handling?

The compiler still generates defensive code even when I don't
use any syntax in my code for them; due, in part, to the
separate compilation requirement, if nothing else.

So, don't use exceptions, right??  But a properly functioning
C++ compiler 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 linkers I'm aware of
don't cover this issue, either.

Take this sequence of code, found as part of some function in
some arbitrary source code file:

       .
       .
       foo ();
       String s;
       foo ();
       .
       .

For purposes here, let's assume this fragment occurs as part of
a function sitting in a module that nowhere in it uses or
handles expections in any way.  Let's say, in fact, that this
module *could* be compiled by a vanilla C compiler, except for
the use of class objects illustrated above.  That's the only
aspect of C++ that's in use, here.  In fact, let's say that this
use of a string is the *only* non-C aspect in the explicit code.
Okay?

Now assume that foo() is an external procedure and that the
compiler has a declaration for, but does not know its definition
at this point in time.

The 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.  So this call to foo() requires nothing
special, no C++ "fluff," so to speak.  The compiler generates
nothing it wouldn't generate if it were a C compiler.

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.  It doesn't matter whether the
function in this module uses exceptions.  Or even if the module
itself ever does.  It only matters that the exception *might*
happen, even outside the module, because foo() is an external
function and the C compiler cannot verify that foo() doesn't
call some other function which *can* throw an exception.

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.  This means that the first foo() gets
coded up differently than the second foo() call.

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.

How about this simple bit of C++ code:

  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.  And this is especially true because of the
semantics of classes, and their associated destructors and
constructors.

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>.  (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 practical reality.

So, don't use classes with constructors and destructors?

Right.

Some more thoughts,

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.  C++ compilers can
automatically generate constructors, destructors, copy
constructors, and assignment operators for you, with unintended
results.

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 (assuming you
can afford exceptions in your embedded code, in the first case.)

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.

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.  So don't use longjump()?

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?

And all the above is just from the cuff -- there is much more.

Jon


Re: C++ in embedded systems
Quoted text here. Click to load it
<lots of good stuff snipped>

Excellent post, Jon. Enlightening.

Steve
http://www.sfdesign.co.uk
http://www.fivetrees.com



Re: C++ in embedded systems
On Thu, 14 Aug 2003 14:32:54 -0700, "Andras Tantos"

Quoted text here. Click to load it

Thanks for the discussion!

I'd love to have and use many features in C++.  Please don't get
me wrong about that.  And for larger embedded systems (which I
generally do NOT work on nearly as much), the debate is entirely
different.

But for small embedded controllers, at least, this is a new area
that needs a lot of testing work and post-analysis of the
results.  So far as I'm aware, that's been missing for small
controller situations and EC++.  There's been a lot of talk, but
where are the detailed comparisons and post-analysis for small
memory situations (such as, say, 2k code space and 256 RAM, just
to toss out one possibility.)

I know that IAR is supposed to have an EC++.  And that may be
one case where the needed real-world testing can take place.
But I'm not going to be the first person to take on EC++ for a
small controller application in the real world, only then to
discover all the things that should have been worked out in a
testing lab, beforehand.  When I see that EC++ has been well
vetted by real compilers in real embedded situations and the
post-analysis papers have been written and are available, I'll
start some tests of my own.  But not until then.

But you can count me in as being very interested.

Jon


Re: C++ in embedded systems
Quoted text here. Click to load it
Should be normal practice :-)
Quoted text here. Click to load it
... snip ...
Quoted text here. Click to load it

I have been wrong before, but I believe that Ada modules (or am I
thinking of Java, or both?) have to specify all exceptions that
they may generate.  Thus code linking to such modules knows what
it must cater for.

--
Chuck F ( snipped-for-privacy@yahoo.com) ( snipped-for-privacy@worldnet.att.net)
   Available for consulting/temporary embedded and systems.
We've slightly trimmed the long signature. Click to see the full one.
Re: C++ in embedded systems

Quoted text here. Click to load it

You should always declare the "big four": constructor, destructor, copy
constructor, and assignment operator. If the default is appropriate, add a
comment to that effect instead of a declaration. If the method is
inappropriate for the class, declare it private so the compiler can catch
inadvertant attempts to use it. (I'm sure this rule is in Meyer's lists in
his Effective C++ books.)

--
Kenneth Porter
http://www.sewingwitch.com/ken /

Re: C++ in embedded systems
On Thu, 14 Aug 2003 18:52:45 GMT, Jonathan Kirwan


Quoted text here. Click to load it

The calls to foo() in the above *are* sematically the same because the
both calls occur within the same scope.  The declaration of the string
object between them doesn't matter.  That the calls may be
implementationally different is a matter to discuss with the compiler
vendor.

C++ syntax allows the variable declaration to be at the point of first
use but the variable may be constructed at any time after the
enclosing scope is opened and prior to its first use.  Your particular
compiler (indeed most compilers) may defer constructing the variable
until its first use but that behavior is not guaranteed.

The generated destructor call in your example has nothing to do with
exceptions per se - the compiler is required to ensure destruction of
automatic objects when they go out of scope.  The possibilty of a
non-local return through foo() changes the how but not the why.  


Quoted text here. Click to load it

Wrong ... "s" is an automatic object.  Even though you explicitly
declared type T to be a struct, the addition of destructor made it an
class. See above.

The automagic promotion of structs to classes is one thing about C++
that I violently disagree with.  All kinds of warnings should go off
when code like the above is written.



Quoted text here. Click to load it

Not gonna argue this one except to say it is not unique to memory
allocation ... the semantics of objects and non-local control
transfers in the same language has certain costs.




Quoted text here. Click to load it

This is no different from a C compiler promoting function parameters
and you aren't complaining about that.

If you have a class A which can be cast to a B and a class C which can
be constructed from a B, then C can be constructed from A.  So what?
This is no different from any other composition of function calls and
it requires the classes to be explicitly written to allow it.

I would call this a case of "know thy program".



Quoted text here. Click to load it

The default assignment operator is usually a bitwise copy (just like
for a struct).   I'm not aware of any compilers that do anything more
intelligent with it.

Personally, I would prefer that the assignment operator not be
generated, but it is and programmers have to be aware of it.



Quoted text here. Click to load it

You've explicitly declared that the base type is all you are prepared
to handle.  Are you complaining that the copy is inefficient or that
you might really want the derived object - perhaps to rethrow it?

If you want the derived object to rethrow, you should catch a pointer
to the base class and dynamically allocate the thrown object.
Naturally, that has its own problems 8-)



Quoted text here. Click to load it

No argument ... this is just something to be aware of.  Turning on
automatic promotion warnings will usually catch this ... as well as
every other nit picking implicit cast you failed to code explicitly.



Quoted text here. Click to load it

There is a school which teaches that constructors should never fail
and that initialization code which can fail should be in an explicit
function.  

Destructors, similarly,  should not be relied on to release any
resources other than memory.  

A lot of C++ example code is badly written.



Quoted text here. Click to load it

setjmp/longjmp are included in the C++ standard library because C++
compilers are also C compilers.  They are definately NOT to be used
when writing C++ code.  

The documentation accompanying the compiler tells you this - along
with telling you that malloc and free are not to be used with objects
and that decorated function names are not compatible with legacy C
code and that ... etc., ad nauseam.

Compilers are not door knobs.  The programmer has to read the
documentation.



Quoted text here. Click to load it

As a guess ... about the same percentage as C programmers who know how
the C runtime works.  Very few programmers nowadays know anything of
how their chosen language accomplishes its magic.  Such things are not
taught (except to CS's studying language implementation) and are
simply not an issue to most application programming.


Quoted text here. Click to load it

C++ was designed with the idea that the program should not have to pay
for features it does not use.  The degree to which compilers achieve
this ideal is open for debate.  Also, most of the overhead is related
to the use of objects rather than exceptions.  


Exception handling effects a non-local transfer of control, just as
longjmp does, but adds controlled destruction of any automatic objects
created between the throw and catch points.  The "unwinding" is of
variable scopes rather than functions per se.  The mechanism used is
compiler dependent, but a simple implementation might look like the
following:

Functions all look something like:

    foo ( jmp_buf chain,  ...   )
    {
        jmp_buf link;
        int error;
        
        error = setjmp(link);
        if ( error == 0 )
        {
            /* do code for foo(), pass link to called functions */
            bar( link,  ... );
        }
        
        {
            /* clean up foo() objects  */
        }

        if  ( error != 0 )
        {
            /* rethrow to next level */
            longjmp( chain, error );
        }
    }

and a try-catch call itself looks something like:

    :
    {    
        jmp_buf trychain;
        int error;

        error = setjmp(trychain);
        if ( error == 0 )  
        {
            foo( trychain, ... ) /* try */
        }    
        else
        if ( error == ... )
        {
            /* caught */
        }
        else
        {
            /* uncaught exception */
        }
    }
    :    

Obviously this simple example doesn't deal with object returns nor
does it capture all the nuances, but demonstrates how easily most of
the job can be done.   Presumably the compiler would eliminate the
cleanup code and (hopefully also) the linkage code for functions that
have no automatic objects.  Such a function needs only pass the
existing chain into any to functions it calls.


Function calls can be statically bound at compile time for classes
that don't use virtual functions and also for virtual function calls
in cases where the compiler is certain of the object's type.
Similarly calls to overloaded functions can be statically determined
at compile time if their parameter types are known.

A simple class costs little more than a C struct: a pointer field in
each object and a generated destuctor call where an automatic object
goes out of scope.  The equivalent of constructors and assignment
operators would have been open coded in C anyway.  Default destructors
do nothing and are eliminated as dead code - anything else would have
been coded anyway.

Run Time Typing adds an extra field in the vtable of each class and
assigns a place-in-heirarchy code to each class.  For single
inheritence the runtime implementation of dynamic cast is just a
pointer dereference to get the class code, a comparison to the
statically known target class code, an offset added to the object
address and an assignment to set the result.  Maybe a half dozen
instructions.

Multiple inheritence can make all runtime type decisions much more
difficult and requires more intrinsic support code.  I'm not greatly
familiar with techniques for handling it so I won't comment further.
But since it isn't necessary for most (all?) programs, most compilers
can disable support for it.



It all comes down to knowing your compiler.  This is true for any
language.  I'll grant that C++ has more to be mindful of than C, but
it does more.   Debating whether a certain mechanism should be hidden
or not is pointless - the gods have decreed that most of C++'s
advanced mechanisms be hidden.  Live with it or don't use it.  

I'm all for making people aware of the pitfalls and tradeoffs of a
particular language (or implementation) but telling people to avoid
using some language because you don't like how it works is a
disservice to everyone.  (Jonathan, you didn't really do this and I'm
not talking specifically to you, but rather to everyone here.  These
language threads can be useful only where there is thoughtful
discussion.  The "mine is better than yours" posts contribute nothing
but noise.)

George

Re: C++ in embedded systems
Hi!

Quoted text here. Click to load it

I'm not an expert, but I was talked that C++ exception handling is one of
the hardest parts for a compiler to implement. It's not hard to handle ~95%
of the cases. But the compiler has to do the right thing for all cases, even
the most wierd corner-cases. To do that you probably have to have a more
complex setup for the simple cases as well.

Quoted text here. Click to load it

The later depends on the compiler. If the BE has enough type-information at
hand, it might be the case. If the BE can't see classes, just function
pointers at that point it hardly has enough knowledge to do this
optimization.

Quoted text here. Click to load it

Overloaded function resolution is always done by the static types and at
compile-time.

Quoted text here. Click to load it

Note, that dynamic-cast is never automatic. If you type such a cast in your
source, you can expect some code to be generated for it.

Regards,
Andras Tantos




Re: C++ in embedded systems
On Sat, 16 Aug 2003 17:26:05 GMT, "Andras Tantos"


Quoted text here. Click to load it

I believe handling all the pathological cases is hard.  But, remember,
C++'s software exceptions are only part of the picture - the compiler
also has to deal with platform specific hardware and OS exceptions.

As I said in my previous post, most of the difficulty comes from
mixing objects with exceptions.


Quoted text here. Click to load it

Yes and no.  If the pointer type is fully specified there is no
problem.  If not, then optimization depends on type inferencing by the
compiler.   The compiler statically knows the type of the function
when the pointer is created because it knows both the class and
function referenced.  


Quoted text here. Click to load it

If the overloads differ by object parameters and the function is
called with a derefenced base class pointer, there is no way for the
compiler to decide statically what type is being passed.  

The C++ spec. does not *disallow* this kind of runtime overloading,
but I don't know of any compilers that implement it.  Every one I have
seen simply goes with the static type of the pointer.


Quoted text here. Click to load it

Absolutely.  I was just responding to Jonathan's question about the
cost of dynamic casting.  The costs depends on the class heirarchy ...
it is quite low for single inheritence models, just a few
instructions, but multiple inheritence models may be much more
expensive.


George

Re: C++ in embedded systems
Hi!

Quoted text here. Click to load it
at

What we're talking about is this:

class A {
public:
    virtual int func(int a) {}
};

class B: public A {
public:
    virtual int func(int a) {}
};

int call1(class A *a) {
    a->func();
}

int call2(class A a) {
    a.func();
}

int main() {
    class A a;
    class B b;
    call1(&b);
    call2(a);
}

Let's suppose this is the whole program and the compiler knows it (using
link-time code-generation or whatever). Now, in call1 the compiler has no
idea what class it gets called with, so it has to do an indirect call (at
first sight). In call2 however 'a' is allways class A and not class B, so it
should be possible to use a direct call. However, by tracking down pointer
types the compiler can deduce that call1 is allways called with a pointer to
'B', so a direct call should suffice there too.

So far, so good. now, let's see what kind of information the compiler needs
to do that!

First of all, it needs to know that the parameter contains a field -
'vtable' - that is constant amongst all 'class A' type parameters but
different for 'class B' type parameters. Note, that this element is filled
in in A::A, it's not a fixed value as far as the back-end (BE) concerned. It
just happens to be filled-in with the same value over and over again. The
way the BE can deduce it to build a call-graph and propagate all constant
values over the call edges. Note, that to do this, you need to track calls
between compilation units, since the constructor, the call-site for
constructor, the member function implementation and the call-sites for the
member-functions are almost allways in different compilation units.

It also has to know that this is a pointer to a function table in the data
segment, that's hidden from the user so that there's no chance that a
conformant C++ code can modify it. It also has to know that the pointer in
that table happens to point to A::func for class A and B::func for class B.

With this much at hand the BE can figure out, that call2 is allways called
with a parameter which has a const field (vtable), and that field points to
a const array of pointers and the value of that array-element that the body
of the function later on refers to happens to be the address of A::func.

If all this information is handed over from the front-end (FE) to the BE
than it's possible to solve the case of call2. Note, that most of the
object-oriented stuff is resolved in the FE and the transfer-language
between the two is usually a symbolic assambly-like langauge. GCC call it
RTL, others call it otherwise. This lagnuage contains references to a symbol
table that might contain type information but it might not be detailed
enough to convey all this information. Also, note all the inherent
information needed about the values in the structures. You not only need
type information but the value of two (const) fields to be able to do this
optimization.

Now, let's move on for the case of call1. If we can deal with call2, it's
relatively easy. All you need to do is to build the call-graph of your whole
program (again), and propagate the type-information over the call edges.
Once you've done that, you'll see that call1-s parameters actual type is
always the same. At this point you can process the function in the same way
you did it with call1 and finally eliminate the indirection.

All in all, the following is needed:
- detailed type information about structs (classes). Especilly type info of
all members.
- const-ness information about data-segment variables and struct (class)
members
- value of const data-segment variables (that's usually available)
- const-propagation among function calls (and compilation units)
- type-propagation among function calls (and compilation units)

I'm not saying it can't be done. I'm saying it's not common at the moment.
Also: it's only possible if you somehow compile the whole program at once
and not using the traditional 'compile all modules individually and link
them later' strategy. I don't know how many compilers support that. As far
as I know GCC doesn't and VC++ does. However I don't know if even VC++ can
do this optimization.

The technique is called 'devirtualization'. There are other methods, not
just the one I've decribed. If you're interested, do a google on it.

Quoted text here. Click to load it

I don't have the C++ spec at hand but I would be surprised. This would
involve pre-generating overloaded functions for all possible
type-combinations in case of templates which would lead to an exponential
explosion of code-size. Also, it requires RTTI, and lengthy lookups before
each function call, for each parameter type. Also, if there's more than one
parameter types to match the lookup can be fairly complex. I would be really
surprised if C++ would allow such an implementation.

If C++ allows that, and one compiler vendor implements that, the same code
compiled with their compiler would behave radically differently than the
same code compiled with other compilers. I don't think the C++ standard
commity would have given that much freedom to the implementors. It's either
required or forbidden. But again, I don't have the C++ std. to look it up.

regrads,
Andras Tantos



Re: C++ in embedded systems

Quoted text here. Click to load it

I'll note in passing that Analog Devices VisualDSP family includes an
inter-module post-processor. This is an optimizer invoked by the linker and
can selectively re-compile modules with new options to optimize the link.

The linker also includes a feature to strip unused objects (functions, data
items). You pass it a root object (typically main) and an exclusion list
(eg. symbols in startup code) and it constructs a tree of used objects and
tosses out anything not in the tree. This eliminates the need to put every
function in a library in a separate file.

--
Kenneth Porter
http://www.sewingwitch.com/ken /

Re: C++ in embedded systems
On Sun, 17 Aug 2003 15:32:47 GMT, "Andras Tantos"

Quoted text here. Click to load it


I was not aware that VC had whole program compilation (or anything
like it).  It has so-called "global" optimization of data access and
function calls, but the optimizations are strictly intra module AFAIK.

In any event, given that the call to virtual func() in "call1" is
indirect, I (currently) don't expect the compiler to be able to do a
great deal with it - the inferencing necessary to do so just hasn't
made it into the compilers yet.


"Call2" is a different matter, however.  You presented a very good
discussion of how the BE, given a fully typed IL might optimize the
call to func() in "call2".  But in concentrating on the BE you forgot
the simplest way of all.

The FE knows the object is always "A" and it also knows the name
(label) of A's func() method which it puts into the vtable for address
resolution by the BE.  The FE does not need to code an indirect access
through the vtable - it can invoke the method directly by label
(remember the first parameter to a non static class method is always a
pointer to the invoking object).  This leaves the BE simply to resolve
the function address.


Quoted text here. Click to load it

What would you do with *all* possible combinations?  No, I think the
compiler needs only to generate the exact typed overloads asked for in
the code and possibly a default to go to when the runtime types don't
match any of the overloads.  Or just do nothing (like a switch with no
default) ... this is C++ after all  8-)


Quoted text here. Click to load it

Yes it requires RTTI, but the lookup would involve only one vtable
access per object parameter and then a table match.


Quoted text here. Click to load it

It's probably something they just didn't consider.  The parameter
conversion rules require that an object parameter must be converted to
the "best fit" object with the single stipulation that no intermediate
temporary object be created.  It does not define "best fit" nor does
it specify that objects passed by pointer are to be converted using
the static type of the pointer.

After a careful reading of the document, I don't see anything which
specifically prohibits a runtime resolution scheme nor do I see any
prohibition of the parameter conversion: [base class] pointer to
runtime object.   No example is given for this particular case so I
can only go by the explanatory text.


Anyway, nobody implement this currently and nobody will because it
would break existing programs.  Therefore it is all academic until the
next standard revision  8-)

George

Site Timeline