Hacker News new | comments | ask | show | jobs | submit login
The cost of forsaking C (bradfieldcs.com)
185 points by zik 11 months ago | hide | past | web | favorite | 136 comments

Knowing C has made me a better and much more versatile programmer than I would be without it. For instance, a while back I had to diagnose a JVM core dumping sporadically, and I was able to use my knowledge of C to try to recover valid return addresses from a corrupted stack, figure out there was a signal handler block on the stack, then further unwind from there to find out the whole story of what likely caused the crash.

Knowing C also helps me write more performant code. Fast Java code ends up looking like C code written in Java. For instance, netty 4 implemented their own custom memory allocator to avoid heap pressure. Cassandra also has their own manual memory pools to try to improve performance, and VoltDB ended up implementing much of their database in C++. I've been able to speed up significant chunks of Java code by putting my "C" hat on.

I would recommend every college student learn C, and learn it from the perspective of an "abstract machine" language, how your C code will interact with the OS and underlying hardware, and what assembly a compiler may generate. I would consider learning C for pedagogical purposes to be much more important than C++.

> I would recommend every college student learn C, and learn it from the perspective of an "abstract machine" language, how your C code will interact with the OS and underlying hardware, and what assembly a compiler may generate.

Do note that these are two very different things. The C abstract machine as defined by the standard is sometimes so different from the actual machines you're going to run your code on, that you get these fine undefined behaviour things that everybody's on about.

Please learn both, indeed, and stress the differences; then highlight the advantages of C. C is a dangerous, but powerful tool, and the necessity of warning students about things should not inhibit learning C.

> C is a dangerous, but powerful tool, and the necessity of warning students about things should not inhibit learning C.

C is sharp, yes. It's also a universal interface. Almost every language has bindings for C, and C has bindings for almost everything.

> Almost every language has bindings for C, and C has bindings for almost everything.

In my mind this is the #1 reason to learn C. And it's unlikely that C++ will replace C in this role anytime soon as C++ has many more ways for an ABI to go wrong.

It often works to implement well to implement your code in C++ and have a C interface (using extern "C") wrapped around it to be consumed be interfaced with by other languages.

I feel the same. Even when debugging JavaScript or C# performance problems I often have insights from a C perspective that people who have only learned Java or JavaScript don't have. I haven't learned much assembly but I think some level of it would also be a very good thing.

Once whole operating systems are written in Rust maybe Rust will be the most important language but until then it's C. C++ doesn't really count. I don't think you can do any serious C++ without knowing C really well.

I was with you until the part about C++. I spent very little part of my career writing C(mostly embedded hobbies) but still managed to pick up all the important bits(memory layout, CPU caches, allocation costs, processor pipelines and the like) from C++.

Really it wasn't too long ago that the STL just wasn't good enough for high performance code and we rolled our own containers/pooling/everything. Modern C++ is a very much a different beast these days.

I think I expressed my thoughts wrong. I didn't mean to say that you need to learn C to use C++ but that if you do any serious C++ work you will know a lot about the way C works. I don't think you will go very far in C++ if you don't understand pointers, heap vs. stack, when to allocate and release memory and a lot of other stuff.

Another important thing is understanding what's going on under the hood with C++ features— like, how you'd compile C++ into C. I don't put a ton of weight in it, but I enjoy engaging interview candidates about what's going on with virtual methods, casting classes, specialization with polymorphism vs templates, "public virtual" and multiple inheritance, etc etc. Like I say, no one thing is super interesting on its own, and its not a textbook answer I'm looking for, more just a sign that someone was curious enough to dig in at some point and figure out how the sausage was made.

Ah, I think we're just arguing semantics then. I see that as very much a part of C++(new vs malloc only has some small subtle differences for instance).

I will say if you don't understand pointers you're in for a world of pain in C++.

> I don't think you will go very far in C++ if you don't understand pointers, heap vs. stack, when to allocate and release memory and a lot of other stuff.

This reads to me as if you ascribe those attributes to C, which simply isn't true; C++ has an equal (to C) concept of pointer, allocation/freeing (and, with RAII, I would argue superior).

> "For instance, a while back I had to diagnose a JVM core dumping sporadically, and I was able to use my knowledge of C to try to recover valid return addresses from a corrupted stack, figure out there was a signal handler block on the stack, then further unwind from there to find out the whole story of what likely caused the crash."

Okay, but is that knowledge coming from coding in C, or it coming from debugging low-level code? I don't know what tool(s) you used to do that debugging, but if I assume for a second that you were using GDB, wasn't the main benefit of your past C experience (in this case) the exposure to tools like GDB, rather than your C knowledge being key in enabling you to dissect that JVM issue?

I think it's the C knowledge. I assume JVM is written in C or C++ so with C knowledge you can understand better how it works.

Can it be replaced with Rust nowadays? Is it low-level enough? Or borrow checker and lifetimes defeat the whole purpose?

For teaching purposes in a university I don't know if rust would be better or not. Keep in mind that the goal isn't necessarily to learn C the language; it's to learn enough of the core language to explore things like:

* How the code is executed on modern hardware and operating systems.

* How various language features might be compiled to assembly

* Memory layouts for things like stack frames, how a simple heap allocator might work, array layouts, layouts for other structures like linked lists, etc.

* The relative cost of various operations and how to write code that is cache friendly.

* How debuggers generally work and how to debug code.

Could these things be taught equally well or better in an undergraduate seeing using rust? Honestly I don't know; I know very little about Rust. I can say that I think C++ is a worse language for this purpose because of the language's complexity and because of features that aren't well suited for the above purposes.

I have heard, for instance, that's it's actually very difficult in rust to write a linked list without dropping to unsafe code. I would consider this a bad thing for the above purposes.

> * How the code is executed on modern hardware and operating systems.

Does anyone know this at this point? With pipelining, out of order execution, vectorisation, register renaming, speculative execution, buffered micro-op fusing and what have you, it's very hard to say general things about how the code executes on the actual hardware.

Perhaps, "Understand the model of how code pretends to be executed"? Pipelining, etc. are meant to produce the same results as the older/simpler systems. You need to understand that that's not quite reality anymore, but as a basic model it's fine.

Sure, but at that point you are content with learning "the x86 virtual machine" -- and is that fundamentally different from being content with learning "the Java virtual machine"?

I'm arguing that it's a matter of degree, not of kind. People make it seem like it's the latter.

It's different in kind. The JVM completely abstracts away things like class loading from the perspective of bytecode - that stuff seems to happen like magic. Ditto exception handling, stack frame linkage, memory allocation, etc.

Viewing all of code and data living together in a big array is important, I think. You don't understand e.g. dynamically generated thunks and shims without seeing the duality of code and data at the execution level.

Short answer: because cache misses are 10x more common and dominate the time saved from pretty much all of those features.

Get your cache misses in order(which is easy to understand in the C model) and you're ahead of 95% of the curve(plus you get a 10-50x performance bump as a reward).

Of course not. With the meltdown/spectre vulnerabilities and mitigations we see that how a processor/OS/program stack executes code can change from year to year or month to month, with significant performance differences. Maybe that's an exceptional blip, maybe it's a portent for the future.

> I have heard, for instance, that's it's actually very difficult in rust to write a linked list without dropping to unsafe code.

It is also hard in C to write a linked list without dropping to unsafe code. In fact, it is hard in C to add frigging signed integers without dropping to unsafe code.

My guess is that for this author's purposes, Rust is in the same place as C++. It can do the low level things, but you tend to spend more of your time working with higher level libraries that hide the nuts-and-blots details like allocation.

Rust libraries don't really hide allocation any more than modern C libraries do. (And if you aren't using modern libraries for stuff like string handling in C, you should be.)

Isn't the point of this whole argument about avoiding abstraction?

I don't see any way in which string.h is less abstract in the "close to metal" sense than a real string or vector library (or Rust) is. The standard C library offers abstractions, just poor ones.

Not sure why this is being downvoted. Whether or not anyone out there likes Rust, dislikes Rust, or is completely apathetic to Rust, we can unanimously agree that the standard string-handling routines in C are quite poor. This isn't a controversial statement.

What modern C libraries do you have in mind?

If you want to "think like a computer" then learn assembly, not C. I'm not joking.

In my experience young devs using C get very confused about wtf pointers are really about and what's the deal with the heap, stack, etc. Whereas they understand the concepts better in assembly despite the verbosity.

I'm not arguing for a deep understanding of assembly but if the goal is to "think like a computer" than at least a basic understanding is incredibly helpful. Or at least less confusing than C.

Learn ARM or some other RISC architecture and not x86/amd64 if you want to learn assembly. x86 and its forever backwards compatable Segmented Memory/CISC architecture makes asm programming painful (not to mention I have learned at least three different syntaxes for x86 asm : (1) MASM/BASM/TASM/NASM, (2) GNU AS (gas), (3) Amsterdam Compiler Kit -- the last two are meant for compiler backends and not human programmers).

I actually cut my teeth on TI's TMS34010 asm. Simple RISC (lot's of registers), flat address space, and bit addressable. I loved it.

Garrch, when I hear bit-addressable I get a twitch since a had to spend a night debugging a RISC system that was failing because a class of RAM didn't work with bit addressable instructions, the system was huge, the error cascaded strangely and the information was hidden somewhere in the 2000 page datasheet.

I can recommend MIPS assembly. It has been the only assembly I've used that actually felt like it was intended for human consumption.

My experience with MIPS is the complete opposite --- simple things like loading a single constant into a register require two instructions, and the lack of addressing modes is also a bit annoying. Breaking dords into bytes and vice-versa involves long sequences of shifts and logical operations, the branch delay slot is confusing to say the least, and the plentiful-yet-unhelpfully-named registers don't really make things any easier. At least ARM's conditional execution and barrel shifter are more interesting and versatile.

Then again, I admittedly learned 8080 and then Z80 and x86 first, so any RISC feels too simple; but those who learned any RISC first probably feel the exact opposite.

> Breaking dords into bytes and vice-versa involves long sequences of shifts and logical operations

modern MIPS has "ext" and "ins", which are probably an improvement in that regard.

> plentiful-yet-unhelpfully-named registers

what's wrong with MIPS register names? my only comparison is x86 and that has been awful. I'm constantly looking up which registers are what.

what's wrong with MIPS register names?

They're not really names, just "addresses" of a different form --- and of course, 0 is not really a register. x86 register names are supposed to be mnemonic, because certain instructions are only usable with, or have shorter forms when used with, the "right" register: Accumulator, Count, Base, Data/Divide, Stack Pointer, Base Pointer, Source Index, Destination Index.

Those explanations for the x86 register mnemonics were great, thanks!

To me the MIPS registers seem pretty logical too, though: $zr for zero, $sp for stack pointer, $a0-3 for arguments, $t0-7 for temporaries and $s0-7 for saved registers. Much better than the plain numbered registers on some assembly flavors.


When I did my undergrad, Ada was the primary language of instruction for first year, with x86 assembler taught as a subject in the second semester.

The idea being to get us thinking about programming correctness from the beginning, then to teach how the machine worked once we had a good foundation.

By second year we were just expected to know C, and coming from assembler, things like pointers were something I never had a problem understanding.

Start high to get a good grounding, go low to understand how everything works, then return high to write code that has a good grounding in how it will be executed on the machine.

> if the goal is to "think like a computer"

If the only goal. The OP listed four.

I'll give shit about #1 when someone invents a time machine and prevents Java from existing.

#2 is a great reason to learn Latin but the "influence of C" is identical to the influence of Fortran or Pascal or Algol and of equivalent magnitude to (what evolved into) Common Lisp or Scheme.

And #4 is true eventually but it only matters when the software is broken.

Well, that's a better answer than I was responding to. I think I disagree a bit on your response to #2, though. Sure, other languages have also contributed mightily to the design of later languages. Some of them might have contributed more than C. OTOH, they haven't contributed nearly as much to the design of libraries and runtimes that are also important for a working engineer to understand. Programs written in any language are loaded into memory in a manner designed to accommodate C. The primary interface between userspace code written in any language and the kernel is via direct syscalls or libc, which were likewise designed with C's needs in mind. When those APIs do change, it's the C definitions that change and other languages are left on their own to parse the new definitions. So it's true that other languages have been just as influential from a computer scientist's perspective, but from a working engineer's perspective no other language comes close.

When is the software not broken? The software is always broken while we're working on it, or we'd have shipped it already, and moved on to the next thing we need to break so we can improve on it - that's the nature of the job.

On one hand yes on the other hand in interest of being pithy I did not say: even when software written in C is broken you're going to need more than a couple semesters of class or a couple months of use to actually fix the C underpinnings of pygtk.

Yes, because that's what I am going to do when/if I build a time machine.

Hey! FORTRAN isnt bad at all so...

To be honest of the four listed reasons I only found the last to be compelling. I did however think the third point was worth commenting on as I don't think C is a good way to achieve that goal.

He stopped reading after one for the sake of sweet karma.

I partly agree with you. But its a fact that you can't just start learning assembly if you have never learned C in the first place. So, young devs, please start learning C first.

This is false. I have learned the basics of assembly without knowing C first. My experience before learning assembly was: Java, Python and some Objective-C (without knowing pointers). So if your point is that you need to learn programming first, then maybe that is true but I highly doubt it. But I can't refute that with my own experience.

Here wa my roadmap:

(1) I first learned assembly by learning computer architecture from Tanenbaum's book -- Structured Computer Organization -- and related course at the Vrije Universiteit Amsterdam. This taught me a toy architecture (Mic1) but it give me a rough idea of how assembly worked.

(2) Then, later I took a course in binary and malware analysis and all that we were required to do was to read x86 assembly in IDA and interact with it via GDB -- and using the command: layout asm, which gives a layout for all the register values and upcoming instructions.

And once I have a debugger available and understand it well enough, I can learn quite well since I can make little predictions and check if they are right or wrong, and so on.

I learned assembly as my first ever programming language and it was just fine. Assembly tends to be simple and can get you coding quickly and understanding the machine as well. It's of course an awfully inefficient way to code in general and that's why we have HLLs but its not true that you need to learn at least one HLL before learning assembler.

That’s simply untrue. Start with a small micro controller, a Microchip PIC for example and you’ll quickly find C is complete overkill.

In the 1980s, most software on 8-bit computers were written in assembly, as compilers for high level languages like C or Pascal were available but 1) used up too much of the (maximum) 64K available 2) generated terrible code.

Forgot the /s?

C is tightly coupled and co-evolved with the Von Neumann architecture. If you understand C you can better understand that architecture, but it's far from the only one. Beyond the world of single core CPUs, systems rarely hyper-specialize to the point that the C/Von Neumann system has (focusing all energies on ALU throughput). And the larger (and more distributed) systems we build, the less they resemble Von Neumann machines.

So while it's realistic to embrace C for many tasks, it's wrong to convince yourself that "the rest follows" from C.

Can you recommend some non-Von Neumann architectures worth learning about? Maybe you agree with these suggestions I found by Googling, or maybe you have other ideas: https://stackoverflow.com/questions/1806490/what-are-some-ex...

The biggest concrete types of Harvard architecture devices I know of are some older small microcontroller architectures (e.g. the PIC10 and PIC12 families) and purpose-built DSPs (e.g. Analog Devices SHARC and TI C6X families). I'm pretty sure that some GPU shaders are also Harvard architecture, but I've heard that mainstream vendors have moved to a von Neumann model.

I second this, I want to know some recommendations too. Are there university courses out there that teach non-Von Neumann architectures? And what are the application areas? I could Google myself but my experience with HN is that if someone knows a good course, then it is a good course. While with Google, not so much.

The Mill architecture:

> the mill uses a novel temporal register addressing scheme, the belt, which has been proposed by Ivan Godard to greatly reduce the complexity of processor hardware, specifically the number of internal registers. It aids understanding to view the belt as a moving conveyor belt where the oldest values drop off the belt and vanish.


What architectures are (1) not incredibly niche, (2) have practical hardware in the wild, and (3) are so divergent from Von Neumann as to dramatically change the principles applicable to C?

I was thinking bigger than a single CPU. For example: 2 computers (even if they're both Von Neumann machines) together comprise a non-Von Neumann system. Some single-device examples are pretty common, like GPUs and NICs.

But what I really had in mind are systems that communicate by passing messages. Distributed systems certainly have an "architecture", but it spans many machines; and communication occurs via messages (RDMA being an exception).

Even modern CPUs contain non-Von Neumann features like multiple cores, pipelines, and out-of-order execution, so the line gets blurry. To a large extent modern CPUs enable C-style programming with a lot of contrivances to hide the fact that they're not quite Von Neumann anymore. Dealing with the different architecture becomes the compiler's job.

"Thinking in C" hinges on the idea that the size of memory is several orders of magnitude larger than the number of cores, and that you can only modify these words one at a time.


Of course those aren't really CPUs to be programmed with software, they're another level of abstraction down. But they're pretty common and hardware description languages are vastly different from C. The inherent massive parallelism of FPGAs & the resulting combinatorial-by-default (sequential only when explicitly declared) languages requires a very different way of thinking.

I still can point to the exact point in my college career that took me to a whole new level of understanding how to program in Java or Python to understanding how things like Garbage Collection, Threads, and other abstractions actually worked. It was an elective I took on Graphics Programming and it was all in C and OpenGL.

Being without GC, I learned the trade offs. Obviously, being able to explicitly allocate and free memory was a big deal. But at the same time, I learned to appreciate the complexity and pitfalls of GC.

Threads are great in Java (as are their parallels in other languages): this is the work to do, put the results here, and everything is cleaned up. Meanwhile, just trying to get the results back from a forked process was CRAZY. Implementing and managing my own shared memory to pass messages back and forth...wtfbbq.

"Think like a computer," not really. More like, "understand what all goes into a thread, an async call, or a garbage collector. Gain more understanding into the corner cases and exactly how much this thing is doing for you. So maybe setting something to null or pre-allocating objects to reduce heap work isn't that big a deal."

C taught me to be patient with higher level languages, to better understand what they were doing for me, be able to reason about how they may be doing these things wrong, and to think critically about how to coax these features into behaving the way I want them to.

Really any lower level language can teach these things. The point here is that C is a nearly universal language that can teach you to reason about these things.

I think learning C is a fine thing to do, when the alternative is to learn nothing at all, as knowing C is a quite useful skill. However, a more substantive question should be, "should you learn C next, or R?" or "should you learn C next, or neural networks"? or "should you learn C next, or Bayesian statistics"? It may be that C is more urgent and important to learn than any of those, but if you find that people are not, it is not necessarily the case that they don't see the usefulness of it. They may just have a finite amount of time, and learning Mandarin Chinese ranks higher for them, not least so they can better understand how natural language processing would work if you're involved with non-European languages.

By all means teach and use C (I do!), but please teach students to refer to C11 or C99, not C89.

Yes. At least C99.

Also, the blog throws in "and C++" in parentheses. C++ is another beast altogether -- it diverged from C a long time ago -- read Scott Meyer's "Effective Modern C++" if you think C and C++ bear any resemblance.

I feel like this article makes two strong arguments in favour of C:

1. You need to know C because C is popular, and

2. You need to know C because C is a lowish-level language.

I can't very well argue with the first point -- indeed, nearly everything fundamental is made in C or the C-like subset of C++ still -- but I oppose myself to the second.

I do think you should learn a lowish-level language, but there are good reasons to make that language something like Ada instead; something high-level enough to have concurrency primitives yet low-level enough to force you to consider the expected ranges of your integers and whether or not you want logical conjuctions to be short-circuiting. Something high-level enough to have RAII managed generic types, yet low-level enough to let you manipulate the bit layout of your records. High-level enough to have exceptions and a good module system for programming in the large yet low level enough to have a built in fixpoint type and access to machine code operations.

Unless you target C specifically for its popularity, there are other options out there, filling the same gap.

>>Ada instead; something high-level enough to have concurrency primitives yet low-level enough to force you to consider the expected ranges of your integers

In practice Ada programmers, because they actually have a proper method of expressing integer ranges, are going to be much more considerate of such things.

Your argument is compelling and crystallized similar thoughts I’ve had. If I ran a CS department I’d follow your recommendations. Practically speaking though another argument against Ada or whatever is drastically reduced reading options.

C++ and Rust address most of the issues presented in the article (with the exception of being able to read the source code of existing software).

My very controversial opinion is that C is obsolete for new programming projects. For example, everything that C can do, C++ can do and better/safer/faster. Rust is also getting there (if not there already). C is not Pareto optimal anymore.

C++ is really overrated, I wish less software was written in it. I'd wager software written in C++ is, on the whole, more buggy than software written in C. Some of the most successful software projects in the world are written in C and occupy the same niche as failing C++ competitors. Top 4 kernels are written in C: NT, Mach, Linux, BSD. C++ kernels like Haiku's kernel are either buggy footnotes or a pipe dream (Fuschia). Git leads comfortably ahead of Mercurial and far ahead of the (numerous) C++ alternatives: can you even name one without looking it up?

Rust (and collectivelly all languages that claim to replace C, or just have a significant cult following insisting that everything be rewritten in them) are fine languages on their own merit but I can't see them replacing C any time soon. The main problem with these is that they were designed by people who don't like C. People who do like C would probably love a "safer" alternative to it and similar to it, but languages like Rust just aren't appealing for the same use-cases. The only language that kind of works for this is Go, which was written by people who, on the whole, do like C.

> Some of the most successful software projects in the world are written in C and occupy the same niche as failing C++ competitors.

You can also find examples for the other way around, too: Web browsers are all written in (mostly) C++ for example.

Games are another example. John Carmack also admitted that switching from C to C++ was the right thing to do for Doom 3:

"Today, I do firmly believe that C++ is the right language for large, multi-developer projects with critical performance requirements, and Tech 5 is a lot better off for the Doom 3 experience." http://fabiensanglard.net/doom3/interviews.php

> Top 4 kernels are written in C: NT, Mach, Linux, BSD.

Parts of NT are written in C++ btw.

I think the article is arguing more for teaching C to new programmers for the educational value, and less for starting serious new projects in C.

I have an issue with the idea of "teaching new practitioners X while telling them that X is not to be used seriously", for so many reasons.

1) It is doing a disservice to the students to tell them that you won't use much of what you are made to learn.

2) A nontrivial amount of people will attempt to use it seriously anyway, sometimes with disastrous results.

3) It usually indicates teacher laziness and/or disinteredness in finding alternative ways to teach.

4) Surprisingly often, when you are not supposed to use X, it is actually useless to learn X. (That is NOT the case in this situation -- there are reasons to learn C -- but the students can't know that unless told so.)

5) There are almost always better alternatives. I have never been unable to find a Y which fulfills the same criteria as X, yet in addition is also usable for serious things. It's just that sometimes you have to look a little harder for it.

In other words, I think it is a teacher's categorical responsibility to find a means to teach what they want in a way that is practically applicable by the students right away. And failing to do so should be taken as a strong hint that what they want to teach may not be the thing they should teach.

I wouldn't say this if I didn't firmly believe that you can teach most things in a way that grants the student immediate practical applications. And I don't say this in a political way -- of course everyone should have the liberty to teach whatever useless thing they want in whatever shitty manner they cn come up with. I'm viewing it more as requirements on any teacher who wants to call themselves good, or tell themselves they are doing their students a service and improving mankind.

In my head, it's not too different from teaching calculus by starting with limits. Sure, once you learn to take a derivative, you're almost never going to use limits directly. But it's still important to know.

Maybe depends on the project. Writing a web API in C almost certainly isn't pareto-optimal. A linux/raspberry-pi driver for your hobby-project that needs to interact with other low-level linux-based software however, C is probably the most efficient fit.

Rust (and maybe Go) do seem like very good long-term replacements for C, but imho it's too early to say that. Until there's a "full stack" of software (OS to user-space/desktop), you still kinda need to know C if you want to be able to understand how your whole computer works.

I don't see Go as any sort of replacement for C. The fact that it's garbage-collected means that it's really not suitable for use in embedded or kernel programming.

AFAICT, Rust was created to be a safer C, and Go was created to be a faster Python; completely separate domains.

You’re quite right.

>> Go was created to be a faster Python

That would surprise me; Go has little in common with Python.

I'd recommend Ada over C for embedded hobby applications; mostly because it will reduce the time you spend debugging by detecting more errors up front and forcing you to be very clear with what you are trying to say. Less frustration and more secure (matters now with internet of things).

But also because it makes it easier to deal with asynchronous actions and has facilities for higher level programming should there be room for it.

C is so unfashionable that the authors have neglected to update it in light of 30 years of progress in software engineering.

Words escape me.

Out-of-context quote. (Even if the original is slightly strangely written.) The "update it" refers to the book The C Programming Language, not to the C language itself; C itself has seen an update as recently as 2011.

It's not out of context at all. It's bizarre to assume that every book ever written about a subject will be updated constantly by it's (currently septagenarian) authors in perpetuity and/or that a failure to update it is an indication of the culture in general and not the authors having literally anything else to do.

It's not like books on C stopped being written by 1990.

Yes, but The C Programming Language is almost always recommended as the book to read.

Still an odd statement, since one of the authors is deceased.

It's fractal.

He means the authors have failed to update the book, The C Programming Language, not C itself.

I would say the authors have failed to ruin a classic by trying to update it. It's short, concise, readable, and useful as a reference. Not sure why anyone would want to mess with it. It was still a fun quip about the age of the language.

In an age when I expect and experience 0.0.X versions of very popular and important Python packages to have major API-breaking changes, it's extremely relevant to point out how a 30-year old, un-updated, book can be useful.

Not just useful, but impressively currently useful. Picking up K&R with little previous experience, and you can make a program that does something in hours, on a current machine. It's dense, but well-written and easy to pick up.

> Forsaking C means forsaking anything below the level of abstraction at which one happens to currently work.

That's trivially true. Pick anything as your bottom abstraction layer, you can almost certainly find something below. Unless you're working on the foundations of mathematics or trying to come up with theories of quantum gravity you haven't hit the bottom of the abstractions.

It's also not as though C is the bottom layer even for programming. There's assembly. Then HDLs like Verilog or VHDL. Then electrical engineering... You have to stop somewhere. My knowledge spans from some electrical engineering through C up to Python/Java/Lisp levels, but I don't (currently) know much of anything about Javascript or actual semiconductor engineering or more than basic physics...

C is pretty good as a foundation for programmers. It's terrible as a foundation for computer engineers, and it's pretty much at or above the ceiling for what's expected of electrical engineers. It's not guaranteed to remain a good foundation for programmers. Massively parallel architectures like GPUs don't tend to fit well into C's abstract machine model.

> Forsaking C means forsaking anything below the level of abstraction at which one happens to currently work.

This is mostly true today, but I hope that over time we'll see more languages fill the gap between hardware and high-level languages. There's no fundamental reason why that middle layer has to be in C, we just have had a shortage of viable alternatives.

There's no shortage of viable alternatives. C++ is significantly more popular than C these days, and the gap widens every year.

The problem is you can't learn all of C++. I've been coding in it for the better part of a decade and I still run into weird corners that don't make sense regularly.

C is much more compact.

Amen. Every time I think I have my head wrapped around C++ a new version comes out or Scott Meyers writes a book and I realize how ignorant I am about C++. No one really know all of C++.

I bet Stroustrap occasionally sees some C++ code and says "huh ... I didn't know you could do that."

> "huh ... I didn't know you could do that."

To be fair, that is not an entirely uncommon thing to say when watching a user work with something you made.

If you truly believe that then I think it's time for you to go learn about some new languages.

C++, Go, Rust... these are very viable alternatives to C.

C++ and Rust, yes, but not Go. Go is a lot closer to Java than it is to C++, since its growable stacks and garbage collector require marshaling overhead to call out to languages that don't know about them.

Hmm, I thought a growable stack via a guard page that allocates more stack on a fault was common in C-based languages. (Though this would be an OS feature rather than a language one.) I routinely put very large buffers on stacks in C and C++ and don't generally run into problems unless I accidentally do infinite recursion.

> I thought a growable stack via a guard page that allocates more stack on a fault was common in C-based languages.

It is, but that's not how Go's growable stack works. Go's is grown by the language runtime and relocated when necessary, using its GC infrastructure, so you really can't put non-Go stack frames on it.

I've heard C language was designed with the idea that the programmer knows what he is doing and the programming language shall not be standing in his way.

For me the cost of forsaking C would be not to experience 100% programming freedom/power (which comes with 100% responsibility). And yes, C also gives you the freedom to do any nonsense :)

I was always fascinated how little resources are needed to run C programs, here an example (not by me): https://github.com/barteksalsa/avr-tiny12-disco/blob/master/... ATTiny12 mikrocontroller with: 1 KiB program flash rom, 32 bytes of "ram", 3 level hardware stack

The infamous quote goes

"The C language combines all the power of assembly language with all the ease-of-use of assembly language."

Do you have a specific example? I wrote programs in Z80, AVR, x86/amd64 and MIPS assembly. In my experience writing C is MUCH easier.

But C allows for just as much foot-shooting, thus the joke. (The quote is meant to be humorous)

> I've heard C language was designed with the idea that the programmer knows what he is doing and the programming language shall not be standing in his way.

It's also been said to have been developed with the idea that compilers for it should be really easy to write, so let's just not spec half the language. ;) Freedom comes as a consequence, not independently.

This is analogous to Unix commands assuming you meant what you said. 'rm /bin/bash' Ok boss, no questions, no confirmation prompt, done.

One of the first things I do on a new Unix login is alias the rm, cp and mv commands to -i so they prompt before clobbering files. I spend a lot of my day at a command prompt, but I like my guardrails.

... and just like aliasing commands you can place your own guardrails in C programs at places where you consider them necessary.

P.S.: alias the rm, cp and mv - that I do too, I think everyone had his 'rm -rf $stuff' moment (-_-)

C does not help you think like a computer. It has no language level support for memory hierarchies or parallelism. It doesn't about know about integer overflow.

While I disagree with your other claims, I'm especially curious about this one:

> no language level support for memory hierarchies

By this, I assume you mean cache hierarchies (for example). What languages do?

None other than various assemblies thanks to the "influence of C" convincing people that a flat (essentially contiguous) endless memory space was "how a computer works"

Typical assembly languages hardly do anything more with cache hierarchies than C does.

No mention of Forth? Small, close to the metal, easier than C, and just as capable. But it's from that alternative universe of computing (also inhabited by Lisp and Smalltalk).

A couple of well-known articles/discussions about that universe:

A Forth Story: https://news.ycombinator.com/item?id=3813966

Lisping at JPL: https://news.ycombinator.com/item?id=7989328

I know these guys and they provide legit college-level CS education. If any of you live in the bay and have an education stipend at work, or just want to level up, then I highly recommend.

Most of the C language texts that I have come across just seem to teach how to use the language, say pointers etc. Maybe there is something about how to use make.

I've found it hard to go from there to link up with the more abstract stuff. Not so with python, because the mental energy I spend reading python code is very little, which enables me to move very fast.

Reading sklearn source has been a revelation to me. I can't imagine having to read a similar high level equivalent in C.


  We give students four reasons for learning C:

  1. It is still one of the most commonly used languages outside of the Bay Area web/mobile startup echo chamber;
  2. C’s influence can be seen in many modern languages;
  3. C helps you think like a computer; and,
  4. Most tools for writing software are written in C (or C++)
[Not really TL, but good context for reading comments referencing the numbers]

> There isn’t even a cute C animal in C’s non-logo on a C decal not stuck to your laptop.

At least there is a cute image presenting “The C Programming Language” in the context of Plan 9: http://9front.org/img/000000wq9nh.png

I guess Cirno counts, her name starts with C!

for rubyists and pythonistas interested in writing more C while not losing the convenience of scripting, lua is probably worth picking up along the way. the following is the C-interop samples from Programming in Lua


... only gripe with this article is the couple of "... and/or C++" clauses: i personally hope to be forgiven for forsaking C++ for new projects, because it is not the 1970's anymore, and i suppose there are alternatives for almost every use case as well as externs for plain-C interop with existing C++ libs.

it's more like C+{100} so many are the additional feature-ughs.

This is a really fun (and relatively short) c programming book: https://www.amazon.com/Expert-C-Programming-Deep-Secrets-ebo...

This book is really good, and I'd say that a couple of weeks after reading K&R - you should read this one (given that you fiddle with C during those weeks).

About not being updated: there are linters, and if you impose a rule that you can only check in code that passed the linter, then essentially you are writing a newer, better version of C.

C is like English. You know it without having to grab a book. It just flows out of you naturally.

how can one of the languages with the most tricky syntax to parse "flow naturally"?

and I say this as somebody proficient in C.

Learn basic digital circuits. Learn an assembly language. Learn C. Implement a simple interpreted language.

You don't have to reinvent every level of the stack and connect it all together(your imagination can fill in the gaps), but this removes the mystery of computing.

> The most recent edition of the canonical C text (the excitingly named The C Programming Language) was published in 1988

So... C99 and C11 aren't things?

C99 and C11 are things; just not the kinds of things for which you update a classic book that describes a reasonably nice language that some of us still prefer to work with (over those things).

From the K&R2's [1988] Preface:

"We have tried to retain the brevity of the first edition. C is not a big language, and it is not well served by a big book".

Well, that line would go out window with a C11 update. Along with, from the introductory chapter:

C is not a "very high level" language, nor a "big" one, and is not specialized to any particular area of application.

Oops; the page count in ISO C more than doubled.

But its absence of restrictions and its generality make it more convenient and effective for many tasks than supposedly more powerful languages.

The absence of restrictions, whereby C programmers were wrestling particular behaviors out of their code with the cooperation from a particular set of compilers, has lately been turned into a license to wreck non-portable code that used to work for decades.

Who wants to update a classic book in the environment in which you have to warn the reader of undefined behavior at every turn.

How do you deal with multi-threading in C89? If you rely on POSIX for your threading support, couldn't you argue that you are now dealing with something just as complicated, if not more complicated than C11?

Sometimes there is just unavoidable complexity for extremely useful features like thread support. Keeping stuff like that out of the standard doesn't necessary make the language simpler if you are forced to rely on other standards anyways ...

> How do you deal with multi-threading in C89?

Certainly not by means of the abhorrent garbage that has been added to C for threading.

POSIX is a standard. POSIX started as a branch of the Unix C library that didn't belong in the language. So for instance that's where "open" and "ioctl" went, while "printf" and "malloc" went to C.

There is no need for C to start adding its own versions of POSIX requirements (let alone toy versions); it just adds complexity.

Now what it means is that we can end up with some mongrel project where we have POSIX threads being spawned in one module, ISO C threads in another and so on.

That sentence refers to the book, The C Programming Language. I admit the sentence is strangely worded.

Yeah. The sentence is misleading, if not mistaken.

That was a misquote. The sentence is:

'The most recent edition of the canonical C text (the excitingly named The C Programming Language) was published in 1988; C is so unfashionable that the authors have neglected to update it in light of 30 years of progress in software engineering.'

Of course you can't see that 'it' refers to the book when you quote it with half the sentence missing.

I read and understood the context. My claim is that calling K&R, last updated in 1988, "the canonical C text" is misleading, if not mistaken. Canonical C has moved on from K&R in the form of the C99 and C11 standards.

Perhaps you are parsing "the canonical C text" differently? I read it as "the canonical 'C text'" and it sounds like perhaps you are reading it as "the 'canonical C' text"

I fail to see how an understanding of C would make one better at understanding userspace process scheduling or text editors.

May I kindly lay out VSCode and Atom as a counter to your second example?

Knowing your allocations and performance at a low level can influence usage of a high level language and have a huge impact.

You take for granted how almost all popular programming languages have inherited the execution model of C.

> I fail to see how an understanding of C would make one better at understanding userspace process scheduling or text editors.

Can't even count how many times I've read the sources of some program to figure out what some random button does (or is supposed to do) -- back when I messed around with blender I used the code more often than the wiki to figure things out and found tons of bugs that way.

Even if it doesn't, there are plenty things it does make one better at understanding, like the subjects mentioned in the article.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact