Ten things I love && hate about C
Preface: Recently a guy asked “Why Should I Ever Use C Again?” — I don’t agree with him, but at least he did say in passing that it’s okay to use C if you’re “coding on a thumb-sized computer” or bootstrapping a language. And I’ll add: Or writing device drivers. Or platform-specific kernels. But anyway …
A few years back I wrote my first web app — in C. I don’t recommend it. For web apps now I’d only use languages beginning with P, particularly P-y. But I was new to the web and to CGI, and I’d come from the world of DOS and TSRs, where using 10KB of RAM was shock-horror-huge.
Now I’m a web programmer, but only by night. By day I code firmware for embedded micros, so C is still my language of choice. By “micro” I mean processors that go in toasters and the like, with maybe 64KB of code space and 2KB of RAM. So your language choices are pretty much assembler and C. (Or Forth — but that’s another story.)
And I’ve found that the more I use C, the less I dislike it. I wanted to write up a bit of a tribute to the world’s most widespread system-level programming language.
So below are five things I like and five things I dislike about C. Feel free to add your own in the comments at the bottom.
1. K&R (love it)
Kernighan & Ritchie’s The C Programming Language is easily the best book about C, and I reckon it’s one of the best books about programming. Short, succinct, and full of useful, non-trivial examples. It’s a good read and a good reference.
Even the preface is good. A quote: “C is not a big language, and it is not well served by a big book.” All programming books would be better if they were limited to this book’s length of 270 pages. It’s quite possible the clear conciseness of K&R has a fair bit to do with C’s success.
The only other programming language book I’ve got similar fondness for is Thinking Forth by Leo Brodie. I’m sure there are other good ones — SICP, for example — it’s just that I haven’t read them yet.
2. It’s concise (love it)
The fact that it’s not a big language is a real bonus. To learn C, you only need to dig its types, flow control, pointer handling, and you’ve pretty much got it. Everything else is a function. The fact that K&R implements qsort()
in 11 lines of this low-level, imperative language is testament to its conciseness.
3. IOCCC (love it)
Call me crazy, but if you’re self-motivated, the International Obfuscated C Code Contest is probably one of the best teachers of computer science out there. And I’m only half kidding. I really think that hackers have risen to the challenge and produced some sweet didactic gems.
One entry I really learned a lot from was OTCC, Fabrice Bellard’s “Obfuscated Tiny C Compiler”. From it I learned about compiler design. Mainly that C compilers don’t have to be great hulking projects with 3.4 million lines of code. But I was also inspired to read Let’s Build a Compiler and sit down and write a mini C-to-Forth compiler.
4. Variables declared like they’re used (love it)
This one’s great for remembering how to declare more complicated things like a pointer to an array of ten integers. Is it int *api[10]
or int (*pai)[10]
? Well, just define it like you’d use it, and all you have to remember is operator precedence: []
binds tighter than *
(which seems to come quite naturally), so yes, you need the parens.
5. It builds a small “hello, world” (love it)
This is particularly good for embedded programming. C doesn’t have a huge run-time overhead. On many embedded processors, a do-nothing app will compile to only 3 or 4 bytes. And a “hello, world” proggy, even under Windows XP, compiles to about 1.5KB (using Tiny C Compiler, which is great for making small executables).
I think if other languages like Python could emulate this (even for a subset of the language), they could win over much of the embedded world.
6. Globals are extern by default (hate it)
“But using globals is bad practice!” you say. Not in embedded systems. Let’s say you have a file timer.c
that has a global int counter
. And in another file, state_machine.c
, you have another counter
. If you accidentally forget to make them both static
, they’re the same variable, and your code is history! No warnings, no nothing …
This seems very odd, especially given that the keyword extern
is right there handy. Once you’re familiar with the two different meanings of static
, it’s easy to avoid, but still.
7. Two different meanings for static
(hate it)
Can somebody explain to me why static
has a totally different meaning inside a function and outside of one? Inside a function it means, well, static — “keep this variable across function calls.” But outside a function it changes completely to mean “this variable’s private to this file”. Why not private
or intern
for the latter?
8. & precedence lower than == (hate it)
For embedded programming one’s always going if ((x&MASK) == 0)
, but you often forget the inner parentheses, because it seems like the precedence of &
should be higher than that of ==
. But it’s not, so you’ve gotta have those extra parens.
However, there’s a good historical reason for this. C was born from B, which only had a single ANDing operator. When Ritchie introduced &&
, they wanted to keep old B-ported code working, so they left the precedence of &
lower than ==
.
9. Macros aren’t quite powerful enough (hate it)
Though recursive #include
s are a very neat idea, how do you do a simple preprocessor loop without resorting to brain-teasers? And, closer to something I’ve needed more than once, how do you give your program an int and string version number with only one thing to modify?
define VERSION_INT 209
define VERSION_STR "2.09"
With the above you’ve always got to change two things when you update your version number. And the special #
and ##
don’t quite do the trick. The only solutions I can figure out involve more work at runtime.
10. It’s not reflective (hate it)
Okay, so maybe this is just re-hashing point 9 — if the macro system were a bit more powerful, the language wouldn’t need to be reflective. And I may be abusing the term. But all I really mean is that with C you can’t write code that writes code.
Why didn’t they make the preprocessor language C itself? This would open up endless possibilities for unrolled loops, powerful macros, and even more IOCCC weirdness. :-)
But I think it’s great how the language fathers have no trouble admitting C’s mistakes. As Dennis Ritchie said:
“C is quirky, flawed, and an enormous success.”
Read his paper The Development of the C Language for more of this — it’s a really good read.
In short, C’s great for what it’s great for.
16 October 2007 by Ben 22 comments
22 comments and pings (oldest first)
Whilst I prefer C++ to C because I seem hard-wired for OOP, I’ve always been amused by language wars.
Just choose the right tool for the job, Python (or Perl or Ruby) for web development… C for device drivers… Prolog for AI etc.
You never hear plumbers arguing that hammers are better than wrenches.
One more reason to love C: It’s the lowest common denominator – you can always extend your language of choice with C and get access to new and dangerous bits of the OS.
People grumble about static “having two different meanings”, but it really doesn’t: it means “this variable is private to the file/block and persistent”. The problem is that the default is either auto (another C keyword, indicating a stack-allocated variable) or global (no keyword; extern is already used to indicate that the variable is declared elsewhere) depending on the context.
I’ve often disliked the limited functionality of the C preprocessor. The preprocessor that comes with most assemblers tends to be much more powerful (preprocessor loops, local macro variables, arithmetic on preprocessor variables, etc.). For a nice preprocessor take a look at NASM, the Netwide Assembler. Its preprocessor has many of these features and the assmbler ships as fairly compact and modular C code, so it’s easy to strip out everything but the preprocessor and recompile
Also, if you have ‘#define X 2.09’, then in your code ‘#X’ evaluates to the string “2.09”
Andy:
“You never hear plumbers arguing that hammers are better than wrenches.”
You do hear them complain about how the last guy did it all wrong. The biggest difference between plumbers and programmers is that the plumber can afford to rip out all of the plumbing :)
You have to get high before you program in Forth. It makes RPN more fluid. ;)
- Two different meanings for static (hate it)
There is one meaning: a global variable that is invisible outside the current scope, be it a function or a file.
Solution for the static thing:
#define private static
:)
… pretend there’s some formatting in there. A hash before the define, and static should be in tt. You know. [Fixed, Bill, thanks. –Ben]
Excellent, well balanced post. Much more even handed than the original “my-blub-is-better-than-your-blub-arrgh-this-language-makes-the- programmer-actually-think” post. Thanks for the nice riposte :)
What are these “other programming language book[s]” you’re comparing K&R to? K&R is pretty boring, as computer books go: just the language, and an uninspiring language at that.
SICP, PAIP, even AIMA are orders of magnitude more fascinating books. Of course, that’s probably because they’re about more interesting languages: C has no syntactic abstraction, so an array is really just an array.
I’m not sure what “non-trivial examples” you found in K&R. My copy doesn’t seem to have any. That’s not a criticism of K&R as a book about C: for such a short book, and in such a low-level language, they really didn’t have room for any.
Re: number 9 (macro preprocessor). I’m not sure it’s a big win, but the preprocessor ends up being a lot more capable than it initially appears. Check out Boost. Preprocessor (admittedly Boost has a C++ focus, but the preprocessor headers are supposed to be C compatible)
#define VERSION_MAJOR 2
#define VERSION_MINOR 09
#define STR(x) #x
#define JOIN(x,y) x ## y
/* some compilers need an extra level of indirection */
#define ISTR(x) STR(x)
#define IJOIN(x,y) JOIN(x,y)
#define VERSION_STR (ISTR(VERSION_MAJOR) "." ISTR(VERSION_MINOR))
#define VERSION_INT IJOIN(VERSION_MAJOR, VERSION_MINOR)
[Sorry about the formatting, Tom. Fixed. –Ben]
David and Paolo, good point about static
. I hadn’t noticed that vars defined static
do actually end up having the same meaning in and outside of a function. What confused me (and others) was what David said about the diferent defaults (what static
is changing it from) — that makes it sure seem like static
itself has two meanings.
Tim, about books, I’m thinking your average 1300-page tome on HTML or Java. About non-trivial examples in K&R: a generic qsort()
, a recursive-descent translator, an implementation of streams and of malloc/free. They might be simple once you know them, but if you can write them in your sleep, you’ll easily get hired — these examples aren’t FizzBuzz.
Tom, good stuff on the version solution! I was hoping somebody might take up the challenge. :-) I had tried more or less that but without the extra level of indirection. Thanks!
Re: cpp
Want a powerful macro processor? We all started using m4 years ago.
Captain Obvious, fair point about m4 — I’ve heard of it but not used it.
Ah, now there’s some useful information! You’re comparing K&R to an “average 1300-page tome on HTML or Java” — though I don’t see how you can use that to conclude that it’s “one of the best books about programming”.
I would call qsort trivial. (In a modern HLL, I can write it in only a couple lines, about the same as FizzBuzz.) SICP implements a compiler. K&R isn’t even in the same league. Sorry — you need to read more books! :-)
Hmmm, Tim, I’m not sure I’d call even qsort
trivial, just because it’s a two-liner in Python and Haskell. It took a top computer scientist to come up with it, and that was 15-odd years into the development of computer science.
Even the simple recursive-descent techniques K&R uses in its dcl
program that are so “obvious” now took years to come up with. Have a read of Jack Crenshaw’s Let’s Build a Compiler, specifically the “A little philosophy” section in chapter 4.
But you’re right, I do need to read more books. :-)
[…] 2. Ten things I love and hate about C […]
Your #6 isn’t right. If you declare two variables as global with the same name, the linker should reject it. All but one ought to be extern. Lazy programmers who want to put the definition (not declaration) in the .h file so they don’t need a separate .c file convinced later compiler/linker writers to ignore that rule. But it wasn’t a problem in the original language definition.
Regarding #6, try compiling one module at a time instead of compiling everything at once, like this:
cc -c -o foo.o foo.c
cc -c -o bar.o bar.c
cc -c -o baz.o baz.c
cc -o baz foo.o bar.o baz.o
Doing the above will result in a linker error in the final build if the same global variable is local to all modules. When you compile all the sources at the same time you’re basically telling the compiler that they all belong to the same module, which is not the case in your example.
[…] 3 good articles written in 10 points for today. 10 cool web development related articles in 2007 Ten things I love && hate about C 10 reasons to choose CakePHP as […]
[…] 4. It’s probably because everybody loves Python. But, I did manage to find a few others: C, Vista, […]
Good show, man. I wrote drivers for quite a while and agree with c’s effectiveness in this area. I used the picc compiler, in fact. Great optimizations for pic develoment.