C++ in embedded systems

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

--
Grant Edwards                   grante             Yow!  Where's th' DAFFY
                                  at               DUCK EXHIBIT??
                               visi.com
Reply to
Grant Edwards
Loading thread data ...

EC++ might be correct to leave out RTTI and C++ EH, but disagree with some of the other decisions. The assumption seems to be that embedded developers are not as competent as desktop developers and that embedded software is much simpler (I'm thinking of mutable ["it might confuse them"] and namespace [embedded software isn't big enough]).

Reply to
Tim

In most "embedded designs," I either use assembler or C++ and assembler. In particular, GCC's in-line assembler is extremely powerful (once you get the hang of it). In my experience, I have not run across any situation where I could find a performance advantage between C and C++ -- if the processor has the architecture and performance headroom to support C it will also support C++.

The C++ advantages and drawbacks are like everything else in a tight design -- you have to be sure you can afford the convenience. This judgment comes from experience, and it may be that most C++ programmers work in the user application space where efficient coding often does not bring performance advantage to the overall enterprise system. After all, if you are shoveling megabytes XML across your 1.544 mb/s internet connection, who gives a flip whether you tighten up some inner loop with hand done assembly language? But for real embedded work, where you are trying to shoehorn the product into the cheapest microprocessor with the fewest resources, here are some advantages over C that C++ brings to the table:

  1. Classes. It's very nice in embedded systems to organize pieces of hardware into objects. For instance, in a very tight AVR project where I am counting cycles, I have a class that wraps the SPI port with an interrupt handler, several command methods, a foreground routine, and logic code to generate and decode packets sent to/from an FPGA. I did it this way to make it simple for me and the FPGA designer to stay on the same page, even editing each others code. But after we were done, I abandoned plans to tighten up its performance. It is better than I could do with straight assembler, and it's more than fast enough.

  1. overridden new/delete operators. I can override new and delete operators, swapping in a custom memory manager to replace "stock" dynamic memory (malloc/free) support. In designs where I need dynamic memory, but can't afford a heap, this is a very nice way to get screaming performance in a manner transparent to the application.

In my experience, here are some C++ features that may or may not be helpful:

  1. Multiple inheritance. This adds a lot of code and overhead to function calls as your "this" pointer is transparently moved about. In some cases, I have used sloppy multiple inheritance to get a prototype running, but after profiling, gone back and replaced some of it with explicit code. However, when simple member functions can be implemented inline, then the optimizer usually completely removes the overhead.

  1. Virtual Functions. If you're playing tricks in C with function pointers to add abstraction, sometimes C++ virtual base classes can do it better with less opportunity for error. This is not always the case, but in my experience it sometimes works this way.

  2. Overloaded operators. I don't like this feature at all. It doesn't really add overhead, but it's confusing to me. A lot of people love it though. Maybe it's a matter of taste.

Even very good people look at me askance when I say I uses C++ for embedded applications. However, it works for me. I am able to bring projects in on time and they work correctly. It's tough times, and I am able to stay very competitive with embedded C++.

Reply to
Ian McBride

eC++ dumbs down the language under the pretense that embedded programmers are too stupid to properly use the deleted features. In reality, I think eC++ is a cop-out by compiler vendors who can't keep up with GNU. They want to change the rules for the sake of their marketing departments.

Reply to
Ian McBride

What?! Have a look at GCC. They have a lot of C99 already in, plus they target a boatload of processors. I use it to cross-compile for Atmel's AVR on the Windows platform.

Reply to
E. Weddington

I tend to agree - I also think that at times C++ gets bad press merely because compiler optimizations are not at the same level of maturity as many C compilers.

FWIW given the current work on g++ and gcc and the huge steps forward that are being made in optimizations (should appear in gcc-3.5) then I think the bar is going to be raised significantly.

For embedded systems use I believe the interprocedural optimizations and the new tree-SSA optimizers will start to be available will allow a level of expressiveness that has just not been possible to date.

This probably means that right now C++ may not be the best of choices at all times, but I suspect that that view will start to change.

I believe the most important contribution that such optimizations will give will be that they will automatically "specialize" generic code in many cases. This will allow embedded developers to focus on good algorithms and good design and leave the tools to generate optimal code (gcc never ceases to surprise me by some of the clever tricks it already does at a high-level, although of course there's always room for improvement ;-)).

I would note that, yes, C++ may not prove to be a particularly good fit for something like a 16-series PIC or an AVR tiny, but then there are many things I can't do in C for those targets either (recursion, heavy use of strings, file I/O etc). As with all things developers need to be aware of the capabilities of the tools they are using and use them appropriately. For some reason software engineers seem to be almost unique in their love of "one solution fits all" approaches - if I want to make a uniform wooden candlestick I'd rather use a lathe, but it seems that all too many software developers would rather keep using the mallet and chisel that they'd used for the last job they did :-(

As with all things, using a new tool effectively requires understanding what it can and can't do, and when it works best. With software engineering this means *really* getting to grips with many development concepts and many programming languages. The singular characteristic I find with really talented embedded software engineers is that they all have a very great breadth of knowledge of such things - they also all make time to learn new things, even if only to conclude that some of those things may not help as much as they first hoped :-)

Regards, Dave

Reply to
Dave Hudson

"Andras Tantos" wrote in news:3f3c0005$ snipped-for-privacy@news.microsoft.com:

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/
Reply to
Kenneth Porter

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

Reply to
George Neuner

ARM's RealView compilation tools have fuller C++ template support than Visual Studio 6.0

Reply to
Tim

: However there are the equivalent of constructors and destructors, : both for complete hash tables and for items to be stored, and : operations to be performed on them. But there is no C++ bloat (it : is written in C) and no known re-entrancy problems.

What C++ bloat?

An implementation with the same features done with C++ is likely to be smaller and faster than a C implementation, because 'void *' is not used which causes aliasing problem and prevents some optimizations. C99 has the 'restrict' keyword these days though..:

formatting link

Also inlining hashing and especially equality comparasion code in C++ implementation likely increases performance and even might reduce size. We'll see what happens..

I'm in the process of writing an implementation using interfaces and inheritance for hash- and equality comparaision function pointers instead. Comparing C implementation template-based implementation isn't probably fair, but that would make even more difference.

The difference between C++ and C implementation isn't going to be very high with a reasoable hash function since it probably dominates, but I guess the difference could be tested by a hash function that runs in a short constant time. Although in C++ implementation returning 0 as a hash value results in the compiler probably detecting it and elimination of few statements, so that's not a very good comparasion either.. I'll try to figure something out.

I'll compile your code and my code using gcc 3.3.1 cross compilers for AVR and H8. Any preference which models?

This will probably take few days since it has been a while I have been programming C++, and I have to make myself more familiar with your test code of hashlib.c so that I can verify my implementation.

Reply to
Jyrki O Saarinen

"Ian Bell" escribió en el mensaje news:3f3cf75a$1 snipped-for-privacy@mk-nntp-1.news.uk.worldonline.com...

I am sure I could implement it with a simple loop.

;-)

Josep

Reply to
Josep Duran

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.

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.

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.

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".

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.

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-)

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.

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.

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.

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.

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

Reply to
George Neuner

: However there are the equivalent of constructors and destructors, : both for complete hash tables and for items to be stored, and : operations to be performed on them. But there is no C++ bloat (it : is written in C) and no known re-entrancy problems.

What do you mean by 'C++ bloat'?

It is not impossible that a C++ implementation of the same interface and algorithm might be less object code and/or faster. I'll report the results when done.. basically I'm using your implementation, but using the tools that C++ provides.

I'll report when done.. I was thinking of using gcc 3.3.1 and AVR and H8 targets.

Reply to
Jyrki O Saarinen

: However there are the equivalent of constructors and destructors, : both for complete hash tables and for items to be stored, and : operations to be performed on them. But there is no C++ bloat (it : is written in C) and no known re-entrancy problems.

What do you mean by 'C++ bloat'?

It is not impossible that a C++ implementation of the same interface and algorithm might be less object code and/or faster. Basically I'm using y our implementation, but using the tools that C++ provides.

I'll report when done.. I was thinking of using gcc 3.3.1 and AVR and H8 targets.

Reply to
Jyrki O Saarinen

Hi!

VS 6.0 was released in '98. RealView is a current product, released in '03 I think. It's not a fair comparision. I have no experience with RealView (I use GCC for ARM targets) but the newest VC++ (7.1) is quite standard-conformant. As far as I remember the standard came out in '98 so VC

6.0 could hardly be fully compilant.

Regards, Andras Tantos

Reply to
Verizon

Right, a "c89" compiler is not required to accept // comments.

And right, Comeau C/C++ can produce c89 c code. Of course, it must still be custom tailored for your platform. Technically, that's neither here nor there though, since the same would have to be true of a native code compiler, although we can be many times quicker to market.

--
Greg Comeau/4.3.3:Full C++03 core language support + more Windows backends
Comeau C/C++ ONLINE ==>     http://www.comeaucomputing.com/tryitout
World Class Compilers:  Breathtaking C++, Amazing C99, Fabulous C90.
Comeau C/C++ with Dinkumware's Libraries... Have you tried it?
Reply to
Greg Comeau

The hash function is external in hashlib. It belongs to the data itself, since what is a good function depends on the data makeup. Similarly the equivalent of constructors and destructors for the items to be stored. The provided string hashing functions are purely for convenience, because strings are probably the most prevalent data keys.

It should all compile out of the box. Supposedly it is in pure portable standard C. The tests depend on the Mersenne Twister (for portable repeatability) which in turn is not guaranteed without a 32 bit unsigned long. hashlib.h has a __cplusplus guard, so it should link to a C++ system immediately.

"splint -weak hashlib.c" generates no warnings. I consider the warnings without -weak spurious, but they might be significant for anyone modifying the code.

Under this system the hashlib module is less than 0x700 bytes of code.

--
Chuck F (cbfalconer@yahoo.com) (cbfalconer@worldnet.att.net)
   Available for consulting/temporary embedded and systems.
     USE worldnet address!
Reply to
CBFalconer

: The hash function is external in hashlib. It belongs to the data : itself, since what is a good function depends on the data makeup.

Naturally. Using function ptr for hashing and cmp'ing two elements is the probably the way of creating a data structure that doesn't care what data is stored there (void *).

BTW the C++ implementation done by using your code was at least smaller on H8 and AVR, performance I couldn't test:

C++:

h8300-hms-gcc -Os -fomit-frame-pointer -ms -c ClosedHash.cpp h8300-hms-objdump --disassemble ClosedHash.o tells there's 0x55a bytes of code

C: h8300-hms-gcc -Os -fomit-frame-pointer -ms -c hashlib.c h8300-hms-objdump --disassemble hashlib.o tells there's 0x5b0 bytes of code

The difference isn't that large though (C version is ~6% larger). So much for the 'bloat' - care to explain what did you mean with it?

Looking at the source, one might be able to squeeze few bytes out by some refactoring of the code.

I'll make a 'report' with more details and make it and the sources available, when I have some time and I have tested my implementation (we'll, yours) against the test suite.

Reply to
Jyrki O Saarinen

You may well be right. A C++ implementation would effectively eliminate the 'master' parameter to all the methods, and auto-supply it via 'this'. It would still need the hshdupefn and hshfreefn constructor/destructors passed in, or some form of reference to the actual data type to be managed. Once you do this I suspect that item access would involve further constructions (dupes) etc., except for the hshdelete operation (which removes the item from management, and returns it to the control of the user).

In C I have a fine control over all this. It will be interesting to see what C++ can do. Unless it has significant extra security or efficiency I see no point to it, since C++ can use the module directly.

--
Chuck F (cbfalconer@yahoo.com) (cbfalconer@worldnet.att.net)
   Available for consulting/temporary embedded and systems.
     USE worldnet address!
Reply to
CBFalconer

Hi!

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.

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.

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

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

Reply to
Andras Tantos

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.