15 November, 2021

My Twitter name says that I’m the old C dog, so you may be surprised to hear that it took me quite a while to warm to C.

How I went to C

I’ve used a few programming languages over the years. There are some that I like, some that I dislike, and some that I’d like to like but don’t. But of all of them, I would say that C hits a sweet spot that most other languages don’t.

I first heard of C when I read about it in Byte magazine in 1983. At the time I didn’t really see the point of it as I was too busy trying to make video games in Z80 and 6502 assembly language. The 8-bit machines that I had access to at the time were already slow, so anything slower than hand-optimised machine code made no sense to me. It didn’t help that on page 324 of the same issue of Byte, Jerry Pournelle complained that C “generates very large programs”, so of course I’d have been put off.

Fast forward a couple of years, and I’d started to hear good things about C, and how it had been used to write this much revered operating system called UNIX. Around the same time, my sixth form college bought an Atari 520ST with a monochrome monitor, about 32 times the amount of RAM that I was used to, a single floppy drive, and a C compiler. I begged my way into borrowing it for the holidays (thanks Ray!) and got absolutely nowhere with it as the manual assumed C knowlege, and I didn’t have access to any resources that would allow me to learn it. Remember, this was the mid 80s, so there was no internet.

Fast forward another year to 1986, and I went to the University of Essex where I learned Modula-2. This was Niklaus Wirth’s attempt at a systems programming language. I liked it very much, despite its arthritis-causing propensity for CAPITALISING ABSOLUTELY EVERY KEYWORD. I also remember learning 68000 assembly language, which was a dream to program, and LISP which was not, and getting up at the crack of dawn to play MUD. But I don’t think I learned C until my second year.

When I did finally learn C (ironically, to write a LISP interpreter), I had mixed feelings about it. I liked the sparse syntax. It was refreshingly brief, and used braces rather than cumbersome keywords, so it made Modula-2 look astonishingly verbose. My fingers certainly enjoyed the break - there are only so many times that you can type IMPLEMENTATION MODULE before you get RSI.

This wasn’t the ANSI C / C90 that many people seem to associate with C. No, this was old school K&R C, and it had some quirky syntax around function definitions in which argument names were separated from their types.

Here’s a simple example - a function that adds two integers. Note that the default return type is int.

add(x, y)               /* name ( argument list, if any ) */
int x;                  /* argument declaration */
int y;                  /* argument declaration */
{
    /* Local variables, if any, had to go here, before the code. This is also
     * true of ANSI-C / C90.
     */
    return x + y;
}

Here’s its modern-day equivalent.

int add(int x, int y)
{
    return x + y;
}

There were no function prototypes. If you had a function that returned a non-int value then you’d have to declare it, but you didn’t declare the arguments. To illustrate, here’s a declaration for atof() in K&R C.

double atof();  /* Convert a string to a double. */

And here’s the modern day equivalent.

double atof(const char* str);   // Convert a string to a double.

So, C was brief, and easy on the fingers, but it was primitive, even compared to its current incarnation. By comparison, Modula-2 was verbose, and stricter about types, but it had two features that C didn’t, namely modules and co-routines.

Modula-2 let you define a public interface to your code in a DEFINITION MODULE (it was a truly shouty language) and implement it in a private IMPLEMENTATION MODULE. Many modern programming languages have modules, but this was the first time that I’d seen them, and had naively expected that they would become a feature of most modern programming languages.

C, by comparison, had (and has) the very primitive notion of using a preprocessor to include header files. This preprocessor isn’t part of the C language. It is just a way of including more text into the thing that you’re compiling, partly so that you don’t have to keep typing common declarations over and over.

Co-routines are less easy to describe succinctly, but if you’ve used a language with async/await, or used yield in Python then you’ve probably got the idea.

I was able to forgive C its lack of modules. It was an older language than Modula-2, and I was convinced that it would eventually get them. However, all it did was evolve slightly, giving us ANSI-C in 1989 which didn’t add much, but made the language a little less quirky and a lot more standardised. But even today, C doesn’t have modules.

I was less surprised that C didn’t have co-routines. From what I’d understood at the time, one of the inspirations for C was BCPL, which had them, but they weren’t added to BCPL until around 1977, which is after C was invented.

By having both modules and co-routines, Modula-2 was probably ahead of its time. Even today, there are languages that have neither. For example, they only made it into C++ in C++20.

So, despite its apparent shortcomings, why did I like C so much?

Unlike assembly language, it was portable, and the code that it generated was usually small enough and fast enough to write games. At the same time, computers themselves started to become far more powerful, so writing C instead of assembly language was a good trade-off. And if you really needed speed, then many C compilers supported inline assembly language anyway.

Unlike Modula-2, it was used on UNIX. My first job out of university was as a Fortran programmer for a little-known real-time system, but within about 6 months I was primarily programming in C on UNIX. And I loved UNIX.

Some background. In the late 80s and early 90s, there was a big move away from the mainframes and minicomputers that had dominated the landscape. At this time, PCs ran MS-DOS and were generally limited to 640KB of RAM, Windows was a party trick that ran on top of MS-DOS, and Macs were expensive and confined largely to publishing. By contrast, UNIX boxes had lots of RAM (ok, 16MB) and let several people use the computer at the same time. The language that you needed to do anything serious in UNIX was C.

C programs compiled quickly and ran quickly, so it was fast. Admittedly, Fortran programs were often faster, but Fortran at the time lacked things like recursive functions (I’m not joking) and user defined data types. My first introduction to socket programming was in Fortran, and that wasn’t pretty. Take a look on page 5-113 of this manual to see what a simple socket server looked like. The same program in C is much more succinct, and far easier to understand, even if the principles are the same.

It was low level. A language that lets you get down to bits and bytes is very useful for certain types of programming.

It was small. This is more of a retrospective opinion, but C is a tiny language. If you look at a C program written by someone else, you can usually figure out what’s going on without having to consult a language reference. This isn’t the case for all languages. For example, I can read C++ written by people at my job, because we have some coding conventions that we stick to, but it will take me much longer to read some arbitrary C++ from GitHub, because there are so many ways of doing the same thing. C++ is a complicated language. And Rust, despite its charms, isn’t much better in that respect, so don’t start!

And that’s the story of how I went to C.