Ten things I love && hate about C

October 2007

Original article on blog.brush.co.nz

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 …

K&R coverA 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 #includes 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.

Comments

version2 17 Oct 2007, 00:02

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.

Andy 17 Oct 2007, 00:40

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.

David 17 Oct 2007, 01:00

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.

jasonp 17 Oct 2007, 01:24

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”

Drew Vogel 17 Oct 2007, 02:24

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 :)

Jerry Ablan 17 Oct 2007, 02:25

You have to get high before you program in Forth. It makes RPN more fluid. ;)

Paolo Bonzini 17 Oct 2007, 02:38

  1. 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.

Bill Weiss 17 Oct 2007, 03:34

Solution for the static thing:

#define private static

:)

Bill Weiss 17 Oct 2007, 03:35

… pretend there’s some formatting in there. A hash before the define, and static should be in tt. You know. [Fixed, Bill, thanks. –Ben]

John Shirley 17 Oct 2007, 04:19

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 :)

tim 17 Oct 2007, 06:12

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.

Tom A 17 Oct 2007, 06:50

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]

Ben 17 Oct 2007, 08:37

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!

Captain Obvious 17 Oct 2007, 09:40

Re: cpp

Want a powerful macro processor? We all started using m4 years ago.

Ben 17 Oct 2007, 09:56

Captain Obvious, fair point about m4 — I’ve heard of it but not used it.

tim 17 Oct 2007, 12:25

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! :-)

Ben 17 Oct 2007, 13:19

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. :-)

Darren 18 Oct 2007, 08:09

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.

Choom 18 Oct 2007, 11:24

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.