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.
What features of C++ should be avoided in embedded systems?
Please classify the answer by reason such as:
EDIT: Lets' use an ARM7TDMI with 64k ram as a target to control the scope of the answers.
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++.
RTTI and Exception Handling:
Templates:
Virtual functions and inheritance:
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:
Just like any other development though, you should balance all of the points you've mentioned against the requirements you were given / derived.
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).
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).
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.
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'.
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.
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.
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.
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.
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);
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.
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.
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:
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.
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.
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.
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.