How embedded projects run into trouble: Jack’s Top Ten – Number Eight
Over the last two weeks I have written about my list of the top ten reasons that projects go sour. (See the list at the end of this article.)
In the movie The Postman (and probably in the book as well; I can’t remember) General Bethlehem enforces a number of laws. The eighth is a corker: the only penalty is death. My number 8 reason for project failure isn’t quite as absolute, but I have seen plenty of disasters stemming from it.
8 – The undisciplined use of C and C++
C is an ancient language dating back to prehistoric 1972. According to a salary survey I did earlier this year that’s older than most embedded developers! It has been refined and improved over the decades but still has its roots grounded in the PDP-11, a wonderful but long-obsolete machine.
C is uniquely suited to deeply-embedded work as it excels at manipulating bits, bytes and hardware. It’s a great choice for real-time work as it doesn’t have some of the problematic features of more modern languages, like garbage collection. C++, of course, brings the advantages of object-oriented programming to the discipline.
But both of these are perilous for non-experts, and even deeply-experienced developers routinely find themselves in difficulty because of some aspects of these languages.
C/C++ have unspecified, implementation-defined, and undefined behaviors. Lots of them. Treading into these snake pits is akin to opening Pandora’s Box. Yes, you can make things work, but the result will not be portable. The mantra behind eXtreme Programming is “everything changes all the time,” which is very true in the crazy-fast-paced world of electronics. Here there be dragons if you use a non-portable feature and must migrate CPU families.
There are plenty of ways to get stung even outside of these behaviors. Consider dynamic memory allocation. That’s a very useful feature for RAM-constrained systems. But it’s fundamentally non-deterministic. Malloc and free, malloc and free, and there’s no way to prove that some strange combination of inputs won’t cause enough heap fragmentation that the requested allocation fails.
When there’s no choice but to use dynamic memory, a disciplined use of C is to check malloc’s return value and take some action if malloc was unable to do its job. Yet I almost never see that test performed.
Then there int. What does that mean? No one really knows; it depends on the compiler, the processor, and the wind direction.
What about pointers? They map directly to machine code where, on the PDP-11 and many other processors since, index registers can directly address memory. Used with discipline, pointers are one of the best parts of C/C++. In the late 1960s Data General’s Nova machine used bit 15 of any address to cause an indirection – you could make the code indirect forever by setting these bits. What a nightmare! But in C we can do the same thing. *******i is just too much for mortals to comprehend. I feel more than two indirects is akin to seppuku.
There are plenty of other ways to get into trouble with a free-spirited approach to pointers. And there are plenty of experienced developers confused by aspects of these. In fact, there’s a 200-page book in print just about this very subject: Understanding and Using C Pointers. 200 pages! Clearly there are plenty of ways to go astray.
In my opinion, C++ is the sort of thing that gives committees bad names. It seemingly has everything grafted on to it, and is full of dark holes best left unused, especially for real-time code. Used with caution it’s an excellent choice for many embedded projects, but I’ve seen too many teams that gleefully exploit every nuance in the 1600 page standard. For example, one project was a replacement for a legacy system, which ran on a 16 bitter with 256 KB of memory. Many bad decisions were made, not the least was the use of C++ in its full glory. After five years and $40m of engineering it was now on a 32 bit machine using up 32 MB of memory, and still had only half the functionality of the legacy product. The project was abandoned.
Plenty of other people have recognized that flamboyant C/C++ use is dangerous and have proposed rules to tame the languages, and to make them reasonable for use in safety-critical applications. MISRA C has about 140 such rules; CERT-C 51. These standards should at least inform the disciplined use of C/C++.
Though it may sound like I’m knocking C/C++ I’m not. These are great choices for embedded work. My complaint is the undisciplined use of the languages.
A martini is good; ten less so.
MISRA C: 2012 Guidelines for the use of C in Critical Systems
CERT C: https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard
Understanding and Using C Pointers, Richard Reese