1. 24
  1. 13

    I found this to be a lovely 30-minute read on C’s motivation, history, design, and surprising success. I marked down over 50 highlights in Instapaper.

    If you haven’t written C in awhile, you should give it a try once more! Some tips: use a modern build of clang for great compiler error messages; use vim (or emacs/vscode) to be reminded C “just works” in editors; use a simple Makefile for build/test ergonomics.

    In writing loops and laying out data in contiguous arrays and structures, remind yourself that C is “just” functions, data atoms, pointers, arrays, structs, and control flow (plus the preprocessor!)

    Marvel at the timeless utility of printf, a five-decade-old debugging Swiss army knife. Remember that to use it, you need to #include <stdio.h>. As Ritchie laments here, C regrettably did not include support for namespaces and modules beyond a global namespace for functions and a crude textual include system.

    Refresh your memory on the common header files that are part of the standard and needed for doing common ops with strings, I/O, or dynamic heap allocation. You can get a nice refresher on those in your browser here:

    https://man.cs50.io/

    Overall, you’ll probably regain some appreciation for the essence of programming, which C represents not due to an intricate programming language design with an extensive library, but instead due to a minimalist language design not too far from assembly, in which you simply must build your own library, or rely directly on OS system calls and facilities, to do anything useful. There is something to be said for languages so small they can truly fit in your head, especially when they are not just small, but also fast, powerful, stable, ubiquitous, and, perhaps, as timeless as anything in computing can be.

    1. 5

      I don’t think that a lack of namespaces is really something to lament. _ is prettier than ::. For all the handwringing you see about them, I’ve literally never seen a symbol clash in C ever.

      1. 6

        I love C and it continues to be my first language of choice for many tasks, but namespaces are the first thing I’d add to the language if I could. Once programs get beyond a certain size, you really need a setting between “visible to one file” and “visible EVERYWHERE”. (You can get some of the same effect by breaking your code up into libraries, but even then the visibility controls are either external to the language or non-portable compiler extensions!)

        And for the record, I’d prefer an overload of the dot operator for namespaces. Or maybe a single-colon operator – some languages had that before the double-colon became ubiquitous.

        1. 2

          I tend to agree that this isn’t a huge issue in practice, especially since so very many large and well-organized C programs have been written (e.g. CPython, redis, nginx, etc.), and the conventions different teams use aren’t too far apart from one another. As you noted, they generally just group related functions together into files and name them using a common function namespace prefix, like ns_. But, clashes are possible, and it has meant C is used much more as a starting point for bespoke and self-contained programs (again, CPython, redis, and nginx are great examples), rather than as a programming environment to wire together many different libraries, as is common in Python, or even Go.

          As dmr describes it in the OP, this is just a “smaller infelicity”.

          Many smaller infelicities exist in the language and its description besides those discussed above, of course. There are also general criticisms to be lodged that transcend detailed points. Chief among these is that the language and its generally-expected environment provide little help for writing very large systems. The naming structure provides only two main levels, ‘external’ (visible everywhere) and ‘internal’ (within a single procedure). An intermediate level of visibility (within a single file of data and procedures) is weakly tied to the language definition. Thus, there is little direct support for modularization, and project designers are forced to create their own conventions.

          1. 3

            I don’t really think that namespaces are the reason people don’t use C for gluing together lots of other C programs and libraries. I think people don’t do that in C because things like Python and Bash are a million times more suitable for it in a million different ways, only one of which is namespaces.

            Large systems don’t need to all be linked together with one big ld call. Large systems should be made up of small systems interacting over standardised IPC mechanisms, each of which of course have their own independent namespaces.

            There’s also the convention we see of lots of tiny files, which is probably not actually necessary today. It made more sense in there days of centralised version control and global file locking in very old version control systems where merging changes from multiple people was difficult or impossible and one person working on a file meant nobody else could. But today, most modules should probably be one file. Why not?

            For example, OpenBSD drivers are usually a single .c file, for example, and they recommend that people porting drivers from other BSDs merge all the files for that driver into one. I actually find this easier to understand: it’s easier for me to navigate one file than a load of files.

        2. 4

          If you haven’t written C in awhile, you should give it a try once more! Some tips: use a modern build of clang for great compiler error messages; use vim (or emacs/vscode) to be reminded C “just works” in editors; use a simple Makefile for build/test ergonomics.

          I am going through the Writing An Interpreter In Go book but in C (which is totally new to me, coming from a JavaScript background) and it’s been the most fun I had in years. I’m actually starting to get quite fond of the language and the tooling around it (like gdb and valgrind).

          1. 2

            I recommend you take a look at http://craftinginterpreters.com as well, if you want something similar for C. The book is in two parts: the first part a very simple AST-walking interpreter written in Java, the second part a more complex interpreter that compiles the language to bytecode and has a VM, closures, GC, and other more complicated features, written in C. If you’ve already read Writing An Interpreter In Go you can probably skip the Java part and just go straight to the C part.

            1. 3

              Thanks, I will (after I’m done with this). I actually really liked it that the book is in Go but my implementation in C, as it made it a bit more exciting for me to think about how I would structure things in C and see what the tradeoffs are versus doing it in Go. Otherwise I’d be tempted to skip entire chapters and just re-use the author’s code, which obviously doesn’t help if my goal is to learn how it’s actually done.

          2. 4

            so small they can truly fit in your head

            Very true. One thing I’ve noticed, going to C from Rust and especially C++, is how little time I spend now looking at language docs, fighting the language or compiler itself, or looking at code and wondering, “WTF does this syntax actually mean?”

            There’s no perfect language though. I do pine sometimes for some of the fancier language features, particularly closures and things which allow you to directly concepts directly in code, like for(auto i : container_type) {...} or .map(|x| { ...}).

            1. 1

              One thing I’ve noticed, going to C from Rust and especially C++, is how little time I spend now looking at language docs, fighting the language or compiler itself, or looking at code and wondering, “WTF does this syntax actually mean?”

              It’s also really nice being able to type:

              man $SOME_FUNCTION
              

              to get the documentation for any function in the standard library (and many others not in the standard library). I do a lot of my development on the train (without access to the internet) and man pages are my best friend.


              On the topic of “wtf does this syntax actually mean” I do think C has some pain points. East const vs west const is still a point of confusion for many, and C’s function pointer syntax will melt your brain if you stare at it too long.

              At one point I wrote a C backend for a compiler I was working on and needed to really understand how declarations worked. I found that this article does a really good job explaining some of the syntax insanity.

            2. 4

              If anyone is looking to give modern C a try I would recommend reading How to C. It’s a short article that bridges the gap between C from K&R Second Edition and C in $CURRENT_YEAR. The article doesn’t cover the more nuanced details of “good” C programming, but I think that K&R + How to C is the best option for people who are learning the C language for the first time.

              1. 2

                Awesome recommendation! As someone who is picking up C for some programming fun & tasks again after a decade-long hiatus (focused on other higher-level languages), this is super useful for me. I have been re-reading K&R 2nd Ed and been looking for something akin to what you shared in “How to C”.

                I also found these two StackOverflow answers helpful. One, on the various C standards:

                https://stackoverflow.com/questions/17206568/what-is-the-difference-between-c-c99-ansi-c-and-gnu-c/17209532#17209532

                The other, on a (modern) set of useful reference books:

                https://stackoverflow.com/questions/562303/the-definitive-c-book-guide-and-list/562377#562377

            Stories with similar links:

            1. The Development of the C Language (1993) via calvin 5 years ago | 16 points | 1 comment