The
C Blues
Copyright 1993,
Jack G. Ganssle
Abstract
C is still a
long way from what we embedded folks need in a language. Here's some ideas
and complaints.
Published in
Embedded Systems Programming, April 1993
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. Pretty 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. Timing
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. Really, 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.