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 hints, tricks and ideas about better ways to build embedded systems, subscribe to The Embedded Muse, a free biweekly e-newsletter. No hype, just down to earth embedded talk. 23,000 other engineers subscribe. It takes just a few seconds (all we need is your email address, which is shared with absolutely no one) to subscribe to the Embedded Muse. |
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.
|