Is there any reason to use C instead of C++ for embedded development?

Piotr Czapla picture Piotr Czapla · May 1, 2009 · Viewed 45.1k times · Source

Question

I have two compilers on my hardware C++ and C89

I'm thinking about using C++ with classes but without polymorphism (to avoid vtables). The main reasons I’d like to use C++ are:

  • I prefer to use “inline” functions instead of macro definitions.
  • I’d like to use namespaces as I prefixes clutter the code.
  • I see C++ a bit type safer mainly because of templates, and verbose casting.
  • I really like overloaded functions and constructors (used for automatic casting).

Do you see any reason to stick with C89 when developing for very limited hardware (4kb of RAM)?

Conclusion

Thank you for your answers, they were really helpful!

I thought the subject through and I will stick with C mainly because:

  1. It is easier to predict actual code in C and this is really important if you have only 4kb of ram.
  2. My team consists mainly of C developers, so advanced C++ features won't be frequently used.
  3. I've found a way to inline functions in my C compiler (C89).

It is hard to accept one answer as you provided so many good answers. Unfortunately I can't create a wiki and accept it, so I will choose one answer that made me think most.

Answer

RBerteig picture RBerteig · May 1, 2009

For a very resource constrained target such as 4KB of RAM, I'd test the waters with some samples before committing a lot of effort that can't be easily ported back into a pure ANSI C implementation.

The Embedded C++ working group did propose a standard subset of the language and a standard subset of the standard library to go with it. I lost track of that effort when the C User's Journal died, unfortunately. It looks like there is an article at Wikipedia, and that the committee still exists.

In an embedded environment, you really have to be careful about memory allocation. To enforce that care, you may need to define the global operator new() and its friends to something that can't be even linked so that you know it isn't used. Placement new on the other hand is likely to be your friend, when used judiciously along with a stable, thread-safe, and latency guaranteed allocation scheme.

Inlined functions won't cause much problem, unless they are big enough that they should have been true functions in the first place. Of course the macros their replacing had that same issue.

Templates, too, may not cause a problem unless their instantiation runs amok. For any template you do use, audit your generated code (the link map may have sufficient clues) to make certain that only the instantiations you intended to use happened.

One other issue that may arise is compatibility with your debugger. It isn't unusual for an otherwise usable hardware debugger to have very limited support for interaction with the original source code. If you effectively must debug in assembly, then the interesting name mangling of C++ can add extra confusion to the task.

RTTI, dynamic casts, multiple inheritance, heavy polymorphism, and exceptions all come with some amount of runtime cost for their use. A few of those features level that cost over the whole program if they are used, others just increase the weight of classes that need them. Know the difference, and choose advanced features wisely with full knowledge of at least a cursory cost/benefit analysis.

In an small embedded environment you will either be linking directly to a real time kernel or running directly on the hardware. Either way, you will need to make certain that your runtime startup code handles C++ specific startup chores correctly. This might be as simple as making sure to use the right linker options, but since it is common to have direct control over the source to the power on reset entry point, you might need to audit that to make certain that it does everything. For example, on a ColdFire platform I worked on, the dev tools shipped with a CRT0.S module that had the C++ initializers present but comment out. If I had used it straight from the box, I would have been mystified by global objects whose constructors had never run at all.

Also, in an embedded environment, it is often necessary to initialize hardware devices before they can be used, and if there is no OS and no boot loader, then it is your code that does that. You will need to remember that constructors for global objects are run before main() is called so you will need to modify your local CRT0.S (or its equivalent) to get that hardware initialization done before the global constructors themselves are called. Obviously, the top of main() is way too late.