C++ covers the whole programming range, from low level to high, making it ideally suited for writing portable software. Code portability is often neglected in embedded-systems engineering, however. With software becoming increasingly complex, and hardware becoming more interchangeable than ever, this oversight can turn into a problem when software must be ported to a new platform. What follows are some tools and techniques that can be used to design and build portable software in C++.
A first step to designing portable software is to choose an operating system that is available for multiple hardware platforms. This makes it possible to switch, relatively painlessly, to a more powerful hardware platform, should the need ever arise-provided, of course, that the platform's respective RTOS is available.
There is still the problem, of course, of what happens if the real-time operating system no longer suits the requirements. The move to a different RTOS can be a real pain if the application programming interfaces of the operating systems differ. Unless the APIs of both operating systems conform to the Posix specification, incompatible APIs will certainly be the norm and all code that interacts with the operating system will need to be rewritten. This is nontrivial and error-prone. Fortunately, many embedded operating systems already support Posix (for example, QNX, Integrity, embedded Linux) or at least provide a wrapper library that provides Posix APIs on top of the operating system's native API (for example, VxWorks). Still, there are major embedded operating systems, like Windows CE, that do not support Posix.
Once it has been decided that C++ is the programming platform, there are a number of things the programmer must be aware of, not least of which is compiler inconsistency. Despite the fact that C++ is designed as a platform-neutral language, writing cross-platform software in C++ is no easy task, especially when it comes to functionality not covered by the C++ standard library.
Especially when it comes to templates, many compilers still have trouble handling C++ correctly. An early version of the DEC C++ (5.6) compiler for OpenVMS could not even parse some of its own standard library header files. Furthermore, the implementations of the standard library that shipped with the various compilers had bugs of their own, to a degree that made them practically unusable in real-world applications (the implementation that came with Microsoft Visual C++ 6 with its multithreading issues comes to mind here). Fortunately, with the latest C++ compiler releases, things are finally getting better.
The C++ standard specifies the exact (required) behavior for most, but not for all, language elements. Undefined behavior means that the standard does not place any requirements whatsoever on the implementation. The compiler may even fail when compiling such a language construct, or the program may silently produce incorrect results.
The rationale behind all this is that it allows the compiler writer to implement certain constructs in a way that best works for the target platform. But that also exposes the code to portability problems. Code that works as expected on one platform can produce unexpected results on another platform. Moreover, the behavior can be different depending on whether the compiler's optimizer has been used.
This article was abstracted from a paper presented at the Embedded Systems Conference Boston 2005 (Class No. ESC-330).
Guenter Obiltschnig (firstname.lastname@example.org), founder and CEO of Applied Informatics (Carinthia, Austria)