Sunday, July 15, 2007

C reflections

I did some work in C/C++ recently, which has left me feeling reflective. I haven't done a lot in C/C++, but I know it well enough to accomplish the goals I must. C has a special place in my memories because it was one of, if not the very first of, the truly general-purpose and professional-strength programming languages I learned about. I'm frightened at the thought of people who say they wish to study the discipline and craft of computer programming but don't know C except by reputation.

What makes that thought frightening is C's continuing vital importance. Just as human history is the invisible-but-pervasive factor that shapes present civilization and culture, so C (specifically software written in C/C++) is the invisible-but-pervasive factor underlying the present software development ecosystem. Show me an OS, a compiler, an IDE, a JVM, a device driver, and I'll show you C's influence.

C's centrality and effectiveness stem directly from its ability to map so closely onto the machine which will execute it, but still abstract the programmer from the excruciating details of the actual hardware--registers, memory segments, byte-order, opcodes. In fact, C/C++ compilers have gotten so adept at bridging the gap, an inexpert programmer who tries to do it himself may achieve a decrease in the optimization level. I know alternatives to C have come along (often not too different from the king), but C's own success has seemingly doomed it to be the de-facto, default choice in its niche.

However, to do real work in C/C++ is to be constantly aware of its balancing act between the structure of the machine and the structure of the problem domain. It's not long after the stage of thinking "I need to work closely with the machine" and therefore choosing C/C++, to the stage of thinking "geez, this is irritating to write all these steps" and therefore breaking out the standard library or one of the multitude of other libraries. I wonder to what degree just the lack of sophisticated built-in string support caused the rise of alternative languages to C for some uses. Then there's the lack of sophisticated built-in data structures such as lists and maps, which are so widely applicable it seems silly to mention it. I caught myself missing stack traces for uncaught exceptions, or even any exception-throwing at all. It's no surprise that some libraries or toolkits provide such a comprehensive cocoon of functions and macros for the programmer that the result feels like a dialect (smart pointers? vectors?).

The tradeoff, naturally, is that since none of those extra libraries or language features must be used in any given program, the overhead or complexity aren't mandatory either. And there's something satisfying in using a language that connects the programmer to the machine. The language has abundant evidence that the program is intended for a computer and not a "theoretical Turing Machine": pointers, structs which are nothing more than organizational units for groups of variables, variables that refer to memory locations rather than objects, the capability to interpret memory contents in multiple ways (hence weak-typing), void * functions for futzing with memory directly. It can certainly be tricky and even error-prone sometimes, but the "garbage in, garbage out" principle rules all. Many people are both more experienced and more skilled at it than I am (I'm more of a math/logic/language tinkerer than a hardware tinkerer).

For me, C is the emblematic programming language, because it unapologetically bridges human thought and machine computation. Someone who can write effective C is someone who can straddle both realms. Try too hard to make C code like human thought, and the program may run horribly (unnecessary recursion, for example, or number overflows?). But try too hard to tailor C code to the machine, and the program may become an inflexible and devilishly cryptic tangle. Get to know C/C++, grasshopper. Folks are fond of saying how Lisp leads to " 'aha' moments". The melding of mind and machine in C code may yield similar " 'aha' moments". At the very least, you may gain a deeper appreciation for what compilers/interpreters are actually doing for you. And if nothing else, having just reading-fluency with C/C++ is pragmatic, because it's still alive and kickin' in the "enterprise", in "legacy" code anyway. C/C++ also happens to be extremely important in the FLOSS world, thanks to this "gcc" thing you may have heard of (during my Gentoo phase, gcc may have been the largest single consumer of CPU cycles).


  1. Interesting, I just posted a journal entry comparing C and Ruby and I touch upon many similar points (although probably less eloquently :)).

    Although I do disagree with the whole "C/C++" nonsense. Coding in C++ as Stroustrup intends is nothing like coding in C. Sure C++ was born from C, but it's not at all the same thing. A good C programmer is not necessarily a good C++ programmer, and vice versa.

  2. Your comment about the intended difference in style between C and C++ is quite right. The (inherited) code that I've been working on is probably more like C-in-C++ than C++. Some parts of it use classes/templates, but most of it doesn't.

    It does make heavy use of a set of functions for working with a dynamically-changing collection of structs, but without C++ features. Taken together with a set of macros, the whole jumble is (to paraphrase Douglas Adams) almost, but not quite, entirely unlike an object lifecycle and an iterator.

  3. This is the year of 2007 and you’re writing these nice things about C!?! Blasphemy :)

  4. I did some work with the Pidgin project a few summers back, and they write it all in C. And let me tell you, when you're done, for some measure of "done", writing some C, you walk away with a noticeable growth of the gonads! The set of languages that have this impact on your manhood is not a singleton, but neither is it very large. Haskell sure has this impact!