tags:

views:

1412

answers:

18

What features of C++ should be avoided in embedded systems?

Please classify the answer by reason such as:

  • memory usage
  • code size
  • speed
  • portability

EDIT: Lets' use an ARM7TDMI with 64k ram as a target to control the scope of the answers.

+10  A: 

Exceptions are likely going to be the most common answer of what to avoid. Most implementations have a fairly large static memory cost, or a runtime memory cost. They also tend to make realtime guarantees harder.

Look here for a pretty good example of a coding standard written for embedded c++.

Greg Rogers
Thanks Greg, that looks to be an excellent document to read, I'm printing it now.
JeffV
Great link! Thanks. I might use this document if we move over to C++ in our robots.
Rocketmagnet
+11  A: 

RTTI and Exception Handling:

  • Increases code-size
  • Decreases performance
  • Can often be replaced by cheaper mechanisms or a better software-design.

Templates:

  • be careful with them if code-size is an issue. If your target CPU has no or only a very tiny ínstruction cache it may reduce the performance as well. (templates tend to bloat code if used without care). Otoh clever meta-programming can decrease the code-size as well. There is no clear cut answer on his.

Virtual functions and inheritance:

  • These are fine for me. I write almost all of my embedded code in C. That does not stop me from using function-pointer tables to mimic virtual functions. They never became a peformance problem.
Nils Pipenbrinck
It is not correct to state "templates always inline". This is just wrong. If you don't specify 'inline' on the declaration of the function it will not be inlined. The linker *should* remove duplicate instantiations.
Richard Corden
true - I'll change the text.
Nils Pipenbrinck
I would still reject that templates are a problem here. There is no "extra" cost in using a template vs writing two versions of the same code without a template. Do you have an explicit example that would show where templates cause otherwise unnecessary bloat?
Richard Corden
The bloat associated with templates has a lot to do with the tool chain and whether or not it can identify and consolidate multiple instantiations of the same type across modules. Many embedded tool chain do NOT consolidate. The other issue if unused methods in the template get optimized out or not.
Tall Jeff
Regarding duplicate instantiations. As Avdi stated you should test your tool chain and then react, but you can also use explicit instantiations.
Richard Corden
Regarding optimisation of template methods, well again, according to strict C++, if you don't use a method it shouldn't even be instantiated! You can also enforce this using explicit instantiations. I'll update my answer below with an example.
Richard Corden
Richard, I'll update my answer with a little template-usage that bloats.
Nils Pipenbrinck
Richard, I'll made some more experiments and will change my answer.. :-)
Nils Pipenbrinck
+1  A: 

I wouldn't have said there's a hard and fast rule to this; it depends a lot on your application. Embedded systems are typically:

  • More constrained in the amount of memory they have available
  • Often run on slower hardware
  • Tend to be closer to hardware i.e. driving it in some way like fiddling with register settings.

Just like any other development though, you should balance all of the points you've mentioned against the requirements you were given / derived.

Jon Cage
A: 

If you are using a development environment targeted toward embedded development or a particular embedded system, it should have limited some of the options for you already. Depending on the resource capabilities of your target, it will turn off some of the aforementioned items (RTTI, exceptions, etc.). This is the easier route to go, rather than keeping in mind what will increase size or memory requirements (although, you should get to know that mentally anyway).

Kris Kumler
A: 

For embedded systems, you'll predominately want to avoid things that have a definite abnormal runtime cost. Some examples: exceptions, and RTTI (to include dynamic_cast and typeid).

luke
A: 

Make sure you know what features are supported by the compiler for your embedded platform and also make sure you know the peculiarities of your platform. For example the TI's CodeComposer compiler does not do automatic template instantiations. As a result, if you want to use STL's sort, you need to instantiate five different things manually. It also does not support streams.

Another example is that you may be using a DSP chip, which does not have hardware support for floating point operations. That means every time you use a float or a double you pay the cost of a function call.

To summarize, know everything there is to know about your embedded platform and your compiler, and then you will know which features to avoid.

Dima
+1  A: 

Regarding code bloat, I think the culprit is much more likely to be inline than templates.

For example:

// foo.h
template <typename T> void foo () { /* some relatively large definition */ }

// b1.cc
#include "foo.h"
void b1 () { foo<int> (); }

// b2.cc
#include "foo.h"
void b2 () { foo<int> (); }

// b3.cc
#include "foo.h"
void b3 () { foo<int> (); }

The linker most likely will merge all the definitions of 'foo' into a single translation unit. Therefore the size of 'foo' is no different to that of any other namespace function.

If your linker doesn't do this, then you can use an explicit instantiation to do that for you:

// foo.h
template <typename T> void foo ();

// foo.cc
#include "foo.h"
template <typename T> void foo () { /* some relatively large definition */ }
template void foo<int> ();        // Definition of 'foo<int>' only in this TU

// b1.cc
#include "foo.h"
void b1 () { foo<int> (); }

// b2.cc
#include "foo.h"
void b2 () { foo<int> (); }

// b3.cc
#include "foo.h"
void b3 () { foo<int> (); }

Now consider the following:

// foo.h
inline void foo () { /* some relatively large definition */ }

// b1.cc
#include "foo.h"
void b1 () { foo (); }

// b2.cc
#include "foo.h"
void b2 () { foo (); }

// b3.cc
#include "foo.h"
void b3 () { foo (); }

If the compiler decides to inline 'foo' for you then you will end up with 3 different copies of 'foo'. No templates in sight!

EDIT: From a comment above from InSciTek Jeff

Using explicit instantiations for the functions that you know will be used only, you can also ensure that all unused functions are removed (which may actually reduce the code size compared with the non template case):

// a.h
template <typename T>
class A
{
public:
  void f1(); // will be called 
  void f2(); // will be called 
  void f3(); // is never called
}


// a.cc
#include "a.h"

template <typename T>
void A<T>::f1 () { /* ... */ }

template <typename T>
void A<T>::f2 () { /* ... */ }

template <typename T>
void A<T>::f3 () { /* ... */ }

template void A<int>::f1 ();
template void A<int>::f2 ();

Unless your tool chain is completely broken, the above will generate code only for 'f1' and 'f2'.

Richard Corden
+10  A: 

Choosing to avoid certain features should always be driven by quantitative analysis of the behavior of your software, on your hardware, with your chosen toolchain, under the constraints your domain entails. There are a lot of conventional wisdom "don'ts" in C++ development which are based on superstition and ancient history rather than hard data. Unfortunately, this often results in a lot of extra workaround code being written to avoid using features that someone, somewhere, had a problem with once upon a time.

Avdi
+3  A: 

Using an ARM7 and assuming you don't have an external MMU, dynamic memory allocation problems can be harder to debug. I'd add "judicious use of new / delete / free / malloc" to the list of guidelines.

Peter K.
Also, dynamic memory allocation happens at runtime, whereas static memory is allocated by the linker. If it is possible to use mostly static data, this will be much faster (in one application I've been working on, I switched from dynamic to static and got a 50 % reduction in execution time).
TomiJ
+4  A: 

The document "Information technology — Programming languages, their environments and system software interfaces — Technical Report on C++ Performance" gives also some good informations about programming in C++ for an embedded device.

jk
Another excellent document, thanks!
JeffV
A: 

One particular problem that surprised me with ATMega GCC 3.something: when I added a virtual ember function to one of my classes, I had to add a virtual destructor. At that point, the linker asked for operator delete(void *). I have no idea why that happens, and adding an empty definition for that operator slolved the problem.

Arkadiy
+2  A: 

If you're using an ARM7TDMI, avoid unaligned memory accesses at all costs.

The basic ARM7TDMI core does not have alignment checking, and will return rotated data when you do an unaligned read. Some implementations have additional circuitry for raising an ABORT exception, but if you don't have one of those implementations, finding bugs due to unaligned accesses is very painful.

Example:

const char x[] = "ARM7TDMI";
unsigned int y = *reinterpret_cast<const unsigned int*>(&x[3]);
printf("%c%c%c%c\n", y, y>>8, y>>16, y>>24);
  • On an x86/x64 CPU, this prints "7TDM".
  • On a SPARC CPU, this dumps core with a bus error.
  • On an ARM7TDMI CPU, this might print something like "7ARM" or "ITDM", assuming that the variable "x" is aligned on a 32-bit boundary (which depends on where "x" is located and what compiler options are in use, etc.) and you are using little-endian mode. It's undefined behavior, but it's pretty much guaranteed not to work the way you want.
bk1e
A: 

Note that the cost of exceptions depends on your code. In one application I profiled (a relatively small one on ARM968), exception support added 2 % to execution time, and code size was increased by 9.5 KB. In this application, exceptions were thrown only in case something seriously bad happened -- i.e. never in practice -- which kept the execution time overhead very low.

TomiJ
+1  A: 

time functions are usually OS dependent (unless you rewrite them). Use your own functions (especially if you have a RTC)

templates are ok to use as long as you have enough space for code - othwerise don't use them

exceptions are not very portable also

printf functions that don't write to a buffer are not portable (you need to be somehow connected to the filesystem to write to a FILE* with printf). Use only sprintf, snprintf and str* functions (strcat, strlen) and of course their wide char corespondents (wcslen...).

If speed is the problem maybe you should use your own containers rather than STL (for example the std::map container to make sure a key is equal does 2 (yes 2) comparisons with the 'less' operator ( a [less than] b == false && b [less than] a == false mean a == b ). 'less' is the only comparison parameter received by the std::map class (and not only). This can lead to some performance loss in critical routines.

templates, exceptions are increasing the code size (you can be sure of this). sometimes even performance is affected when having a larger code.

memory allocation functions probably need to be rewritten also because they are OS dependent in many ways (especially when dealing with thread safety memory allocation).

malloc uses the _end variable (declared usually in the linker script) to allocate memory but this is not thread safe in "unknown" environments.

sometimes you should use Thumb rather than Arm mode. It can improve performance.

So for 64k memory I would say that C++ with some of its nice features (STL, exceptions etc) can be overkill. I would definitely choose C.

Iulian Şerbănoiu
+2  A: 

It's an interesting read for the Rationale on the early Embedded C++ standrard

See this article on EC++ as well.

The Embedded C++ std was a proper subset of C++, i.e. it has no additions. The following language features were removed:

  • Multiple inheritance
  • Virtual base classes
  • Run-time type information (typeid)
  • New style casts (static_cast, dynamic_cast, reinterpret_cast and const_cast)
  • The mutable type qualifier
  • Namespaces
  • Exceptions
  • Templates

It's noted on the wiki page that Bjarne Stroustrup says (of the EC++ std), "To the best of my knowledge EC++ is dead (2004), and if it isn't it ought to be." Stroustrup goes on to recommend the document referenced by Prakash's answer.

Zing-
EC++ mostly tried to solve the problems of C++ vendors, not progarmmers.
MSalters
+1  A: 

In most systems you do not want to use new / delete unless you have overridden them with your own implementation that pulls from your own managed heap. Yes, it'll be work but you are dealing with a memory constrained system.

dwj
+1  A: 

Having used both the GCC ARM compiler and the ARM's own SDT I'd have the following comments:

  • The ARM SDT produces tighter, faster code but is very expensive (>Eur5k per seat!). At my previous job we used this compiler and it was ok.

  • The GCC ARM tools works very well though and it's what I use on my own projects (GBA/DS).

  • Use 'thumb' mode as this reduces code size significantly. On 16 bit bus variants of the ARM (such as the GBA) there is also a speed advantage.

  • 64k is seriously small for C++ development. I'd use C & Assembler in that environment.

On such a small platform you'll have to be careful of stack usage. Avoid recursion, large automatic (local) data structures etc. Heap usage will also be an issue (new, malloc etc). C will give you more control of these issues.

Tim Ring
A: 

I wrote a small little "article" about embedded programming with C++ in this thread:

http://stackoverflow.com/questions/3407003/where-to-find-information-on-embedded-c/3451301#3451301

I hope it can be useful for you.

Max Kielland