The C Blues
C is still a long way from what we embedded folks need in a language. Here's some ideas and complaints.
Published in ESP, April, 1993
|For novel ideas about building embedded systems (both hardware and firmware), join the 27,000+ engineers who subscribe to The Embedded Muse, a free biweekly newsletter. The Muse has no hype, no vendor PR. It takes just a few seconds (just enter your email, which is shared with absolutely no one) to subscribe.|
By Jack Ganssle
A number of developers called about my January column ("Defensive Programming"). Everyone agrees that some sort of style standard is needed, but few feel the standards should be enforced with the rigid discipline of a neo-fascist. Most want room for creative expression of their commenting, naming conventions, and the like.
Bah. Humbug. Software written without the benefit of a uniform standard is immediately crippled. It doesn't really matter what the standard is, as long as it is uniformly applied, and it strives to make the code more readable.
Others agree to style standards in principle, but want to defer implementing a particular standard until we, as a software community, figure out what the best standard is. After all, there is little consensus on the "proper" way to write or document code. Every organization and pundit has a different solution.
Tom Peters would refer to waiting for perfection as: ready, aim, aim, aim... Sometimes it is better to just do something. In other words: Ready, fire, aim.
And then came my favorite argument of all: C makes code inherently readable. (A variant of this is: I use long variable names in C; I don't need comments). Hmm. This just does not wash. Code does. Comments explain.
All embedded folks love C. It makes our code helpful, wise, obedient, trustworthy, loyal, and all the rest. My feeling about C is: Yuk. Phew. Phoowie.
Reading C hurts my eyes.
I distrust any language which promotes convoluted code contests. Some magazines sponsor such events. My Thursday night "guys night out" crowd thrives on that line of C which seems to do one thing, but in fact does something quite the opposite.
Admittedly, C is fun to work with. Oh, the joys of twiddling bits! It's a breathe of fresh air from the tedium of assembly language, and it gives one a rush of power COBOL programmers will never understand. I maintain, though, that C is a cruddy language ill suited to the needs of generating maintainable code, and that only though heroic efforts (i.e., a rigidly enforced set of software standards) can it serve our primary goals of generating code that works, and that is maintainable.
Most of my complaints are against the use and abuse of the language, rather than against C itself. Specifically, I feel far too many programmers take advantage of the forgiving nature of the compiler to generate crummy code. In addition, the C compilers themselves generally do not provide the resources we need to write decent embedded code.
Long ago I owned a succession of Volkswagen beetles. Part of the experience of owning these cars was the constant engine maintenance. One of the best books about servicing the car was by John Muir, an aging hippie auto mechanic. His writing exhorted us to keep the engine clean. His reasoning was simple: a clean engine is easy to work on. The shadetree mechanic won't mind doing a quick oil change if he doesn't get filthy in the process.
Muir's words hold wisdom for programmers. Clean code is a joy to maintain. It's clear what each line does, and there's little worry that a minor change will cause major problems in some other part of the system.
Above all, clean code is pretty. I take as much pride in how my code looks as in how well it performs - and sometimes maybe a little more pride in its appearance. While it may seem a preoccupation with trivia, if I compare the crap I wrote (which worked) 20 years ago with the pretty code I write today (which also works), the difference is profound. If I went on a job interview, I'd bring the recent, pretty code to show my skills... even if it didn't work!
Pretty code follows some indentation standard, so the structure is visible with a glance. Pretty code is never overly indented - twelve nested IFs are too many for any mere mortal to understand.
Pretty code is well commented (above all), but the comments are inserted in uniform fashion. One of C's weaknesses is its free form - it does not encourage pretty comments.
In assembly language tab stops insured that with even a tiny bit of effort the comments all line up on the right side of the code. Writing pretty assembly code is easy.
It's hard to make pretty comments in C. Function headers can be beautiful, but what is the best way to comment individual lines? I really like assembly's right hand comments, since one can scan down the page and immediately pick out the purpose of each line without ever reading the code itself. In C I place comments starting in line 41 (a tab stop), to strive towards the commenting columns of assembly, but too often the C code exceeds the 41 column limit. In this case I'll break the code into two or more lines. I'm not entirely happy with the result.
I'm inclined to think that the answer will be found in better technology. Microsoft's Quick C for Windows displays all of the comments in green - they immediately jump out at the reader. It sounds a little tacky until you see it in action. Given the tools to change comment colors and a color printer, this may be the answer.
This compiler also highlights keywords (like IF and PRINTF) in blue, which helps break a huge file into much more discernible segments. Color may be the extra dimension we need to generate understandable code.
This gets to my next complaint about C. Nearly all compilers behave as if the interface were an ancient ASR-33 teletype. Developers are the last holdout of anti-Windows, anti-GUI, sentiment, most likely because the GUI-based tools of today make no use of the resources modern interfaces bring to bear.
For example, why do you use a text editor? Think about it... your computer most likely has AmiPro for Windows, or Word, or some other word processor on the disk; tools that let you manipulate text in any imaginable fashion. Yet, we programmers still use text-based character editors to pound in code, since the binary formatting commands inserted in document files by a decent word processor will completely blow the compiler's mind. So, we who are inventing new technologies and constantly redefining the state of the art slave away at old-fashioned text editors.
Dream for a minute about writing code in Microsoft Word. You could define a template to specify a function header. Then, instantly, all headers will follow the same format. (Clever readers will then realize that some other tool could go in and extract the headers, automatically generating documentation packages with no a priori knowledge of the language).
Tabs for indenting will be standard, and not based on the useless 8 character standard defined for assembly language decades ago.
Stealing an idea from Quick-C, my Word-based code would have all comments in some color different than that of the code. Comments on the right side of the code will all be in a larger font, making them leap out even when printed in black and white. I'd highlight variables and function calls in different colors or fonts, again making them more visible.
Can you imagine providing internal documentation by including graphic timing diagrams? Embedded code often generates or responds to sequences of signals which we usually try to document by tediously drawing wave forms with asterisks and minus signs. How much nicer it would be to paste in a drawing made in CorelDraw!
Of course, writing under Word (or some other decent word processor) gives instant compatibility with other documents the company produces, such as the user manual and on-line help. Much time could be saved by cutting and pasting between the code and these documents, rather than reformatting plain tab-encrusted text.
As it is, working under a GUI it's all but impossible to import and display or print code generated with a text editor, as the tabs and proportional fonts skew the code's prettiness. It's incredible to report that I sometimes have to go to DOS to print a listing file, since only DOS has such poor formatting to blindly dump text and ignore proportional fonts and all.
While bitching about compilers... to my knowledge, most C compilers have been written as if we learned nothing from assemblers and compilers for other languages. In assembly language I never print the raw code; I print a listing file, which is modified by the actions of the assembler. Simple niceties of this include the PAGE directive, that let you align a new section of code at the top of a page. In C it seems the only way to do this is by embedded form feeds, a crude and ugly solution at best. C is much easier to read when each function starts on a new page.
Similarly, assembler listing files include a user-defined line on the start of each page, with perhaps a secondary line as well. Most programmers put the project name and version on the first line, with the name of the current subroutine on the second. Every book has chapter and page info on each page... shouldn't your code have this as well?
And, if you wrote under Word, the word processor could automatically create a table of contents, a valuable addition to working with large programs.
One of the unique things about embedded code is that it often faces real time constraints. The best code in the world is junk if it cannot keep up with outside events. How do we deal with this in C?
The answer is appalling. Generally, we take a wild guess, write some code, and see how well it does. If it isn't fast enough, we'll change something and try again. There seems to be no other answer, since there's no way to tell how long a line of C takes to execute. Why do we allow our tools to put us in this ludicrous position?
Some assemblers generate timing information in the listing file. The number of T-states per instruction are listed, giving precise elapsed time data. Knowing the clock rate, wait states, and the like, it's a simple matter to convert T-states to time. Why don't compilers do the same?
Similarly, how long does any particular library function take to execute? A sine computation may be fast enough to meet real time requirements... or it may not. It's silly to be forced to write code to, essentially, document the compiler's behavior.
Of course, complex library routines may not have totally predictable responses. A trig function's response will vary greatly depending on the input argument. Still, the vendors should provide at least a range of results to give us, their customers, a clue.
The few assembly language die-hards complain either that C is not fast enough, or that the language insulates programmers too much from the raw machine. Both arguments are valid at times; both could be overcome by a compiler that provides more information about what it is doing. I don't want to see the assembly code it generates; that's just too much detail. I would like to know about timing and memory use, though.
Of course, this is just griping that I hope some compiler vendor sees and takes to heart. C is wonderful, as long as it is supported with rigorous standards. In my business we use C for all embedded work. Even interrupt service routines are coded in C, though we do use reasonable tools to measure the performance of critical routines.