
Should you learn C to “learn how the computer works”? - steveklabnik
https://words.steveklabnik.com/should-you-learn-c-to-learn-how-the-computer-works
======
Sir_Cmpwn
C doesn't necessarily teach you how computers work. But it does teach you how
software works. Our modern software empire is built on mountains of C, and
deference to C is pervasive throughout higher-level software design.

>You may have heard another slogan when talking about C: “C is portable
assembler.” If you think about this slogan for a minute, you’ll also find that
if it’s true, C cannot be how the computer works: there are many kinds of
different computers, with different architectures

As with many things, this phrase is an approximation. C is a portable
implementation of many useful behaviors which map reasonably closely to tasks
commonly done on most instruction sets. C programmers rarely write assembly to
optimize (usually to capture those arch-specific features which are
unrepresentable in C), but programmers in other languages often reach for C to
write the high-performance parts of their code. The same reason C programmers
in the early days would reach for assembly is now the reason higher-level
programmers reach for C.

~~~
ashleyn
C at the very least teaches the difference between stack and heap memory, a
crucial concept obscured by most higher-level languages.

~~~
XMPPwocky
Here's the C99 standard:

[http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf](http://www.open-
std.org/jtc1/sc22/wg14/www/docs/n1256.pdf)

There are no occurrences of the words "stack" or "heap" in this document.

What the spec actually discusses is "storage durations". Now, in many cases
you can say, well, "automatic storage duration" means it's on the stack, but
that's not something C has any opinions about.

If you want to know about the stack and the heap, saying "learn C, and then
learn how these abstract C concepts map onto the stack and the heap" might not
actually be the best way to figure this stuff out.

What actually ends up happening, in my experience, is that to figure C out you
have to get a decent mental model of how the stack and heap work, and then,
from that, say "automatic storage duration and malloc/free are basically just
the stack and the heap".

I learned calculus as a kid because I needed it for an online electronics
class I was taking. But I'm not going to recommend that folks take electronics
classes so they learn calculus! (that said- having a motivation to learn
something, having an application in mind, a problem you want to solve...
certainly seems to make learning easier.)

~~~
drb91
People don’t learn C by learning the spec, and virtually every c
implementation and runtime I know, it uses a stack and heap.

Oddly you seem to recognize this, so I’m not sure what your point is.

~~~
bunderbunder
At some point, these conversations seem to always turn into a contest to see
who can be the most pedantic. Which I think is pretty fun - and pretty well in
the spirit of the original article - but that's me, and I can't fault anyone
for being turned off by it.

's funny, I am certainly aware of having, at some point, at least skimmed over
the C99 standard encountered the concept of a storage duration. But, aside
from that brief few hours, virtually the entirety of my C-knowing career has
been spent thinking and talking in terms of stacks and heaps.

Meanwhile, in my .NET career, I really did (and, I guess, still do) feel like
it was important to keep track of how .NET's distinction is between reference
types and value types, and whether or not they were stack or heap allocated
was an implementation detail.

------
munificent
I like this article a lot. There are two ways you can think of looking at the
field of programming:

* As a continuum from "low level" to "high level".

* As a giant bag of topics: strings, heap, hash tables, machine learning, garbage collection, function, instruction set, etc.

If your goal is to have a broad understanding of CS, you want to explore the
whole continuum and many topics. C is great for that because it exists at a
sweet spot that's lower-level than most languages but not so low level that
you have to jump into the deep end of modern CPU architectures which are
fantastically complex.

Because C has been so successful, there are few other successful languages
that sit near it on that line. Those that are (C++, Rust) are _much_ more
complex. So if your goal is just to get familiar with that region of the
continuum and not become an expert in a new language, C has a good
price/performance ratio.

Also, it's a good language for getting exposure to many topics other languages
hide. If all you know is JS or Ruby, C will teach manual memory management,
static types, heap versus stack allocation, pointers versus values, primitive
fixed-sized arrays, structs, and bit manipulation. Its sparse standard library
means you'll end up implementing many common data structures yourself from
scratch, so you'll get a better understanding of growable arrays, linked
lists, trees, strings, hash tables, etc.

"Portable assembly" is a nice slogan for C. But it's worth remembering that
back when it was coined, the emphasis was on "portable", not "assembly". At
the time, C was an alternative to assembly languages, not higher-level
languages.

It's _never_ been very close to an assembly language. C has types while
assembly is untyped. C has function calls that abstract the calling convention
while assembly languages make that explicit. C implicitly converts between
types, assembly doesn't.

~~~
steveklabnik
Thank you!

> if your goal is just to get familiar with that region of the continuum and
> not become an expert in a new language, C has a good price/performance
> ratio.

I think this is a particularly great point in your post. I wonder what
AndyKelley thinks of this, as in my understanding, that's sort of what Zig is
trying to do as well. That is, Zig is attempting to be a language on a
specific spot on the price/performance ratio, as it were.

~~~
AndyKelley
I agree with this characterization. Zig is trying to directly replace the
niche that C represents, in terms of exactly this tradeoff.

So if Zig is successful, in 4 years the title of this post would have been
"Should you learn C/Zig to 'learn how the computer works'?" and in 8 years the
title would have been "Should you learn Zig to 'learn how the computer
works'?". :-)

~~~
steveklabnik
Excellent :)

------
CJefferson
I teach C. It's an increasingly terrible way of learning "how a computer
works". You would be much better off learning a little assembler.

The problem is most of the pain of learning C comes from undefined behaviour,
which isn't how "computers work". The fact that depending on optimisation
level writing past the end of an array might write to memory, or might not, is
(mostly) a unique feature of C. Similarly sometimes a signed integer will
overflow "cleanly", but then sometimes undefined behaviour will kick in and
you'll get weird results. This (ironically) makes checking for overflow when
adding integers in C annoyingly complicated.

With assembler if you write through a pointer, you write to that memory
location, regardless of if you "should" be right now. You do multiplication or
addition and you get well-defined 2s-complement wrap-around.

~~~
T-hawk
> You do multiplication or addition and you get well-defined 2s-complement
> wrap-around.

You get that, if that's what your CPU implements. Of course that's what all
commonly used processors nowadays like x86 do. But C was meant to run on
weirder ISAs as well. The specs of C allow so much undefined behavior in order
to let C just emit the instructions for the multiply or memory access, and C
deliberately says "not my problem" for whatever this architecture happens to
do with edge-cases like overflow.

Using assembly instead of C cuts past the abstraction of the architecture, for
both good and bad. You get defined behavior but lose portability. Practically
speaking, yes, x86 assembly probably is a better way to learn without getting
distracted by forty-year-old hardware oddities.

------
sharkbot
As someone who felt that C was the path to knowledge for how modern computer
systems "work", Forth and QEMU have become my "stretch challenge" for those
with the motivation to tinker.

For me, working thru the resources on the OSDev wiki by taking jonesforth and
linking it with the bootstrap from the "Writing an OS in Rust" tutorial
([https://os.phil-opp.com/](https://os.phil-opp.com/)) really showed me how
far C is from the hardware, and how much closer Forth connects to the
idiosyncrasies of a computer system. Further, the immediacy of the Forth REPL
can help with little experiments and building up the understanding of how much
a machine's architecture influences code/data structures, opcode choice,
privilege management, etc.

Plus, understanding Forth will bend your brain in a very strange but positive
way :)

~~~
stochastic_monk
I’m convinced that Forth is a valuable intellectual exercise.

Can you elaborate on how you feel Forth better matches how a computer actually
works? I’m not yet convinced on that point, but I have no Forth experience.

~~~
mjevans
The most fun, in a tinkering sense, that I've ever had with low level
programming was in a variant of Forth (within Minecraft, years ago when the
mod that included it was still up to date).

That experience made me regret that Forth wasn't part of my formal education
experience: it is a wonderful slightly above assembly language.

Forth is what should be included in a BIOS as the absolute lowest level
interpreted language. A basic machine abstraction could be made by using only
interpreted functions/procedures and that could be used to bootstrap add-in
routines for attached hardware. It would be very possible to write low level
bootstrap drivers that could be used on any architecture providing the
specification (including some interface hooks for defining how to register and
use an IO interface).

~~~
rhn_mk1
I'm not sure if you're posting this with awareness, but for the public benefit
I'll say that this had been done, ages ago indeed.

[https://en.wikipedia.org/wiki/Open_Firmware](https://en.wikipedia.org/wiki/Open_Firmware)

I've had the pleasure to interact with it while trying to get a SPARC Ultra 60
workstation to work. The driver hooks missing from most modern hardware meant
that not all graphics cards could be used with full support - currently BIOS
drivers provided on some PCI hardware (RAID, network adapters) are only for
the x86 architecture. I forgot what they are called though...

------
GolDDranks
What really made computers click to me was reading a book that had the
premise: "learn just enough assembly to be able to implement C features by
hand; we'll show you how". Sadly, I don't remember the title.

Another revelation much later on, was, as discussed here, the realisation that
C is indeed defined over an abstract machine. I think much of those
realisations were because of reading about how crazy compiler optimizations
can be and how UB can _actually_ do anything.

~~~
another-cuppa
Sounds a bit like "Programming from the Ground Up" [1].

[1]
[https://savannah.nongnu.org/projects/pgubook/](https://savannah.nongnu.org/projects/pgubook/)

~~~
hondadriver
Or maybe just Professional Assembly Language [1]

[1] [http://www.wrox.com/WileyCDA/WroxTitle/Professional-
Assembly...](http://www.wrox.com/WileyCDA/WroxTitle/Professional-Assembly-
Language.productCd-0764579010.html)

~~~
bigmit37
Will check this out as well as the book above. Thank you.

------
joshe
One valuable property of C that the author didn't hit, C code is easily
translatable into assembler in your head. He kind of misses this point with
the virtual machine discussion. Yes C code becomes different types of assembly
by platform, but you can look at C and have a clear idea of what the assembly
will look like.

This is at a really good level for driving intuitions about what the computer
is actually doing. Your concern 99% of the time when you are trying to think
at this level is performance, and thinking in C will give you the right
intuition about how many instructions your code is generating, what and when
memory is being allocated, and which exact bytes are being pulled off the disk
for certain function calls.

Modern swift/java/javascript compilers are so good that they will often
generate better code than you write. This often makes knowing C a less useful
skill. But, even so, when trying to understand what a compiler optimization is
doing, you are probably thinking in terms of the C code it is generating. It's
at exactly the right level to be clear about what is actually happening
without drowning yourself in JUMP statements.

~~~
saghul
> One valuable property of C that the author didn't hit, C code is easily
> translatable into assembler in your head. He kind of misses this point with
> the virtual machine discussion. Yes C code becomes different types of
> assembly by platform, but you can look at C and have a clear idea of what
> the assembly will look like.

I've seen this many times, I'll be honest: I can write C, but for the life of
me I don't have the slightest clue how the assembly code will look like.

~~~
Koshkin
Why not simply imagine it as something like this:

    
    
      ; c = a + b
    
      load  reg1, a
      load  reg2, b
      add   reg1, reg2
      store reg1, c

~~~
freeone3000
That's the correct steps, but that doesn't add anything that the original C
code doesn't do.

On x86, that could be: mov [ebx], eax add [ecx], eax

Which is an entirely separate set of operands, instructions, and addressing
modes. It's still the same steps, but it's not really telling you anything.

------
chacham15
I find this article to be disingenuous. Yes, C isnt "how a computer really
works". Neither is assembly. The way a computer works is based off of
transistors and some concepts built on top of that (ALUs for example).
However, there is no need to know about any of that because you're presented
with an abstraction (assembly). And thats really what people mean when they
say C is closer to how a computer actually works: its a language with fewer
abstractions than many others (most notably, its lack of garbage collection,
object oriented behaviors, and a small runtime). That lack of abstraction
means that you have to implement those concepts if you want to use them which
will give you an understanding of how those abstractions work in the language
that has them built in.

~~~
jerf
But in addition to the mismatch between the abstractions provided and the real
hardware, C _qua_ C is _missing_ a huge number of abstractions that are how
real hardware works, especially if we pick C99 as Steve did, which doesn't
have threading since that came later. I don't think it has any support for any
sort of vector instruction (MMX and its followons), it doesn't know anything
about your graphics card which by raw FLOPS may well be the majority of your
machine's actual power, I don't think C99 has a memory model, and the list of
things it doesn't know about goes on a long ways.

You can get to them from C, but via extensions. They're often very thin and
will let you learn a lot about how the computer works, but it's reasonable to
say that it's not really "C" at that point.

C also has more runtime that most people realize since it often functions as a
de facto runtime for the whole system. It has particular concepts about how
the stack works, how the heap works, how function calls work, and so on. It's
thicker than you realize because you swim through it's abstractions like a
fish through water, but, technically, they are not fundamental to _how a
computer works_. A computer does not need to implement a stack and a heap and
have copy-based functions and so on. There's a lot more accidental history in
C than a casual programmer may realize. If nothing else compare CPU
programming to GPU programming. Even when the latter uses "something rather
C-ish", the resemblance to C only goes so deep.

There's also some self-fulfilling prophecy in the "C is how the computer
works", too. Why do none of our languages have first-class understanding of
the cache hierarchy? Well, we have a flat memory model in C, and that's how we
all think about it. We spend a _lot_ of silicon _forcing_ our computers to
"work like C does" (the specification for how registers work may as well read
"we need to run C code very quickly no matter how much register renaming costs
us in silicon"), and every year, that is becoming a slightly worse idea than
last year as the divergence continues.

~~~
hfdgiutdryg
_A computer does not need to implement a stack_

What general purpose computer exists that doesn't have a stack? Push/pop have
been fundamental to all the architectures I've used.

~~~
tom_
RISC-type architectures with a branch-and-link instruction (as opposed to a
jsr- or call-type instruction) generally have a stack by convention only,
because the CPU doesn't need one to operate. (For handling interrupts and
exceptions there is usually some other mechanism for storing the old program
counter.)

~~~
hfdgiutdryg
Can you point me to a RISC architecture that doesn't have push and pop
instructions?

~~~
monocasa
Nearly all of them?

ARM's pop is really a generic ldmia with update. You can use the same
instruction in a memcpy.

MIPS, PowerPC, and Alpha don't have anything like push and pop, splitting
load/stores and SP increment/decrement into separate instructions.

AArch64 has a dedicated stack pointer, but no explicit push pop.

In general the RISC style is to allocate a stack frame and use regular loads
and stores off of SP rather than push and pop.

------
haberman
C teaches you how a computer works because the C abstract machine is defined
such that operations that must be manually performed in assembly language must
also be manually performed in C.

C doesn't let you write something like:

    
    
        string x = y;
    

...because to actually create a copy of a string the computer must allocate
memory, copy memory, and eventually free the memory. In C each of these steps
is manual. Higher-level languages support high-level operations like the
above, which obscures how much work a machine has to actually do to implement
them.

~~~
fixermark
Well... C teaches you how a PDP-11 worked, but modern computers aren't PDP-11s
either. Most happen to expose a PDP-11-like structure via x86 assembly, but
even that abstraction is a bit of a lie relative to what's going on under-the-
hood.

C doesn't let you write "string x = y" because it doesn't have string as a
primitive variable type; that's the whole and only reason. It's not quite
correct to say that "the computer must allocate memory, copy memory, and
eventually free memory" to do string assignment---it depends heavily on
context and how a given language defines 'string' (are strings mutable in this
language? If not, that statement may just be setting pointers to two immutable
memory locations the same, and that immutability may be maintaned by type
constructs in the language or by logical constraints in the underlying
architecture like read-only TEXT memory pages---or both!---and so on).

The old Apple Foundation NSString is an interesting example of how the
question of string manipulation is a complicated one that doesn't even lend
itself well to the "allocate, copy, free" abstraction you've described. Pop
that thing open, and you discover there's a ton of weird and grungy work going
on inside of it to make string comparison, merging and splitting of strings,
copying strings, and mutating strings cheap. There are multiple
representations of the string attached to the wrapper structure, and a host of
logic that keeps them copy-on-X synchronized on an as-needed basis and selects
the cheapest representation for a given operation.

~~~
haberman
> C doesn't let you write "string x = y" because it doesn't have string as a
> primitive variable type; that's the whole and only reason.

But why does C not have string as a primitive variable type? It's for exactly
the reason you state: there are very different approaches a high-level
language can take wrt. string ownership/mutability. These approaches might
require GC, refcounting, allocation, O(n) copies, etc -- operations that do
not trivially map to assembly language.

C is close to the machine _precisely because_ it declines to implement any
high-level semantics like these.

~~~
fixermark
I wouldn't call "lacks a feature" the same as "close to the machine" any more
than I'd call "specifically refrains from defining whether 'a' or 'b' is first
evaluated in an expression like 'a - b'" as "close to the machine." Machines
lack statements and expressions entirely, but C has those; the reason that C
refrains from being explicit on order of evaluation for (-) and other
operators is that on some machines, evaluating b first, then a, then
difference, lets you save a couple of assembly instructions at compile-time,
and on some other machines, the reverse is true. So C's ambiguity lets the
compiler generate code that is that much faster or terser on both machines (at
the cost of, well, being Goddamn ambiguous and putting the mental burden of
that ambiguity on the programmer ;) ).

Perhaps it is more correct to say not that C is "closer to the machine," but
that C "tries to position itself as somewhat equidistant from many machines
and therefore has design quirks that are dictated by the hardware quirks of
several dozen PDP-esque assembly architectures it's trying to span." Quite a
few of C's quirks could go away entirely if someone came along, waved a magic
wand, and re-wrote history to make K&R C a language targeted exclusively at
compiling Intel-based x86 assembly computer achitectures, or even one specific
CPU.

------
blaisio
I definitely would not consider a CS education complete without C. If you
don't know C, that means you don't know how how parts of operating systems
work. It definitely makes you a less useful engineer.

I don't think people need to be able to code in C at the drop of a hat. But it
should not be a scary thing for good engineers.

~~~
lmm
> If you don't know C, that means you don't know how how parts of operating
> systems work.

How so? It's not like you can't write operating systems in other, more
readable and understandable, languages.

~~~
mschaef
The historical dominance of C over the last 30-40 years is, by itself, enough
to justify C as a core part of a computer science curriculum.

------
Animats
What C teaches is that the underlying memory model is a flat, uniform, byte-
addressed address space.

One of the consequences of C is the extinguishing of machine architectures
where the underlying memory model is not a flat, uniform, byte-addressed
address space. Such as Symbolics or Burroughs architectures, or word-addressed
machines.

~~~
steveklabnik
We have our differences, but you’re totally correct here, and I’m not sure why
you’re downvoted.

The “byte addressable” thing is exactly why C was created over B, even, right?
That was one of the crucial features not supported.

~~~
icedchai
I don't think he is correct. The underlying representation of a pointer is not
defined by the C standard. You could have a C implementation that works on
segmented, non-flat architectures. Look at the C compilers from the DOS days,
for example...

~~~
steveklabnik
I think we're talking past each other, and in some ways, this is what the post
is about.

The C abstract machine presents a flat, uniform, byte-addressed address
space.[1] The reason that it does not define what a pointer's representation
is is because it needs to map that to what the hardware actually does, which
is your point.

1: Actually, my impression is that it does, but this is actually an area of
the spec in which I'm less sure. I'm going to do some digging. Regardless, the
point is that the memory model != the machine model, which is your point.

EDIT: For example,
[https://en.cppreference.com/w/c/language/memory_model](https://en.cppreference.com/w/c/language/memory_model)

> The data storage (memory) available to a C program is one or more contiguous
> sequences of bytes. Each byte in memory has a unique address.

Though cppreference is not the spec itself, of course. It is the conceptual
model that's often presented.

LAST EDIT: I happened to run into someone who seemed knowledgeable on this
topic on Reddit:
[https://www.reddit.com/r/programming/comments/9kruju/should_...](https://www.reddit.com/r/programming/comments/9kruju/should_you_learn_c_to_learn_how_the_computer_works/e71l1zg/)

TL;DR no! This is another misconception. Tricky!

~~~
icedchai
Yep, these common assumptions are not always true if you read deep enough into
the spec. Even though they may be true in practice, only as a side effect in
most implementations...

I taught myself C when I was a teenager (on an Amiga, back in the 80's.) It is
amazing that almost 30 years later, I'm still learning new things about it.

------
fixermark
This is an excellent essay, and touches on a lot of the key ideas both of what
C is and of how C maps to the hardware. In particular, I completely agree with
where Steve lands here, which is that C can give you more understanding of how
computers work but won't necessarily reveal some "deep ultimate truth" about
the zen of computers. Especially in light of this essay recently shared on HN
([https://blog.erratasec.com/2015/03/x86-is-high-level-
languag...](https://blog.erratasec.com/2015/03/x86-is-high-level-
language.html)), which makes the excellent point that even a language that is
compiling down to raw x86 assembly is still operating in a "virtual machine"
relative to what goes on in the actual chip and the flow of electrons through
the silicon-metal substrate.

It's abstractions all the way down.

------
kens
Coming from an operating system background, I find the article's use of
"virtual machine" very bizarre. The article confuses very different things by
stating that "'runtime', 'virtual machine' and 'abstract machine' are
different words for the same fundamental thing".

~~~
shrimpx
How do you feel about the "Java Virtual Machine" then?

~~~
iaabtpbtpnn
Or LLVM, the Low Level Virtual Machine?

~~~
steveklabnik
... which now just LLVM, because it's so confusing!
[http://lists.llvm.org/pipermail/llvm-
dev/2011-December/04644...](http://lists.llvm.org/pipermail/llvm-
dev/2011-December/046445.html)

------
asveikau
The part that's closer to how the computer works is the part that lets you
write an allocator.

Witness any discussion of a pointer bug at any level of the stack. No matter
what the piece, someone will jump in and say, "that should have been bounds
checked!" A lot of times it's true. But what they miss is that _the bounds
check needs to come from somewhere_ , which means there is a layer of the
system where it doesn't apply. Somewhere you have a larger piece of memory,
and it gets divided into smaller chunks, and the boundaries are fiction,
meaningful only to a higher level abstraction. This is the way it needs to
work.

Reducing the code in which that's a concern is likely legitimate. But then you
enter into "that's not really how it works".

------
guyzero
Do you need to learn C to have a successful career in CS or any other field?
No. Should you learn it? Yes.

Do you need to bake bread to eat it? No. Should you learn to bake it? Yes.

There are lots of things you should learn to do because they're useful and
teach you about how the world works. The miracle of society is that you don't
have to learn most of them to enjoy using them.

~~~
parliament32
That bread analogy is on point. The old quote comes to mind,

>A human being should be able to change a diaper, plan an invasion, butcher a
hog, conn a ship, design a building, write a sonnet, balance accounts, build a
wall, set a bone, comfort the dying, take orders, give orders, cooperate, act
alone, solve equations, analyse a new problem, pitch manure, program a
computer, cook a tasty meal, fight efficiently, die gallantly. Specialization
is for insects. -- Robert Heinlein

~~~
fermienrico
I disagree with the bread analogy being on point.

For one, it misses the fact that learning how to bake it doesn't benefit the
eating process. Whereas, learning how to code in C benefits (but is not
required) in general programming.

I understand that analogies are always compromises because there is always
going to be something that differs from the original motif. Also, analogies
should be used when the motif is complicated with many layers of abstraction
and using one _adds_ something - whether it is clarity, succicicty or
reduction of abstraction. In the case of parent comment, it is not too
difficult to understand the original motif. So, the bread analogy adds
virtually nothing to it.

~~~
steveklabnik
> For one, it misses the fact that learning how to bake it doesn't benefit the
> eating process.

Maybe it works differently for bread, but with whisky, learning how it has
made has certainly enhanced my consumption, if only to give me the language
needed to describe flavors and compare and contrast.

------
jcranmer
On the topic of "C is portable assembler":

When you ask what portable assembler is, there's actually three different
things you could mean:

1\. C is portable assembler because every statement maps pretty directly to
assembly. Obviously, compiler optimizations tend to make this completely not
true (and most of the rants directed towards compiler developers are precisely
because they're eschewing naive mappings).

2\. You can represent every (reasonable) assembly listing using C code. Well,
except that C has no notion of SIMD value (yes, there's extensions to add
vector support). Until C11, it couldn't describe memory ordering. Even with
compiler extensions, C still doesn't describe traps very well--any trap, in
fact, is undefined behavior.

3\. Every assembly instruction can have its semantics described using C code
more or less natively. Again, here C has an abstract machine that doesn't
correspond very well to hardware. There's no notion of things such as flag
registers, and control registers are minimal (largely limited to floating-
point control state). Vectors and traps are completely ignored in the model.
Even more noticeably, C assigns types to values, whereas processor definitions
assigns types to operations instead of values.

Note here that I didn't need to invoke undefined behavior to show how C fares
poorly as portable assembler. The problem isn't that C has undefined behavior;
it's that a lot of machine semantics just don't correspond well to the
abstract semantics of C (or indeed most languages).

------
sys_64738
C conceptually is the nearest layer to the hardware that you'll get outside
assembler. What that means is that most C operations can be easily translated
into the machine code equivalents and that makes debugging of compiled code so
much easier. A disassembly of many binaries will reveal their C roots by
function calls being equivalent to jump statements, as an example. If you have
the C source then you can usually figure out what's going on.

That simplicity and the smallness of the C language makes it truer to the
hardware underneath and as you don't need to descend through layers of
frameworks, like a high level language such as python, to understand what the
heck is going on.

~~~
geofft
> _What that means is that most C operations can be easily translated into the
> machine code equivalents and that makes debugging of compiled code so much
> easier. A disassembly of many binaries will reveal their C roots by function
> calls being equivalent to jump statements, as an example. If you have the C
> source then you can usually figure out what 's going on._

This is very much not true in my experience. Optimization (even just -O1,
which is required to make C be more worth writing than Python for large-scale
apps) will do things like reorder statements, inline functions, elide local
variables, set up tail calls, etc. It is a specific skill requiring experience
to be able to understand what's happening when stepping through a C function
in a debugger, even with source at hand and debugging information included. If
you haven't been frustrated at "Value has been optimized out" or breakpoints
being mystically skipped, you haven't done enough debugging work to really
acquire this skill, and saying that C maps to the machine is just theory.

There is value in a highly unoptimized language toolchain for debugging, sure.
But honestly CPython is closer to that than any production C binary you're
likely to see.

~~~
jimmaswell
Why would you turn on optimization while debugging?

~~~
tomjakubowski
Sometimes the bugs don't happen when optimizations are off. (-:

~~~
jolmg
That'd be a compiler bug, then. I don't think those would be common.

EDIT: I suppose the other possibility is that the program is doing some weird
things, like reading its own machine code from memory.

~~~
geofft
Neither of the above are required: your code might be racing with some other
API. If your code finishes fast enough, the other code isn't ready for you.
With optimizations off, you consistently lose the race and the bug doesn't
show up.

------
kev009
No, you should learn one or more assembly languages and read some arch
reference manuals and their supporting glue chip reference manuals if you want
to learn "how the computer works" from the perspective of programming after
reading an academic book or two on the subject of computer architecture.

C is useful for participating in commercial application of these things, but
it is possible to be a very accomplished C developer and have no idea how
hardware interfacing works and I'd wager this category vastly outnumbers those
that understand hardware interfacing.

------
kahlonel
Depends on what do you mean by "work" here, which is a very vague term. One
can argue that assembly doesn't teach you how computers work either because it
"abstracts away" how transistors do their job. The point is: Depending on how
deep you want to go in your understanding of the underlying system, there is
always a level of abstraction that you have to settle on before building your
knowledge on top of it.

Turns out that using C as the programming language for your project puts you
at a very balanced spot in the abstraction hierarchy, where you don't have to
learn processor specific instructions (well, not all of them) while requiring
you to fully understand the "programmer's model" of the target processor. Keep
in mind that learning C syntax alone doesn't do jack shit. You HAVE to be able
to make full use of the tooling that a C toolchain provides. That means you
need to learn things like memory layout (linker scripts, injecting code at a
particular memory address / ISRs / faults and handlers), boot process (startup
scripts, stack setup / data-segment initialization), kernel/user-mode and what
not. So yeah, C is THE best language to learn your target system because of
the level of optimization of the abstraction of the hardware that it provides
you. Your code may be getting translated from a standard C architecture to the
specific machine architecture; nobody's stopping you from learning how that
process takes place. You could choose to learn the exact process, which C
provides you complete tools for, or you could choose to ignore that process
and let your compiler do its job. The beauty lies in the many possible levels
of control.

~~~
pjmlp
I can get all of that from languages like Basic, Pascal or Ada, while enjoying
safer code.

[https://www.mikroe.com/compilers](https://www.mikroe.com/compilers)

[https://www.ptc.com/en/products/developer-
tools/apexada](https://www.ptc.com/en/products/developer-tools/apexada)

------
tdsamardzhiev
Back in the day, people told me that after learning C, learning anything else
about programming would be much easier.

I think I understand now. C hits a sweet spot between high and low level
programming, allowing you to unobstructedly move either up or down the
abstraction hierarchy.

Transforming a piece of C code to machine instructions in your head is
manageable, and so is transforming high-level abstractions to C code.

------
hamilyon2
C teaches you how computer works because a lot of important things are written
in some dialect of C. Operating systems and compilers, most important of them.

Surely, if you can manipulate your computer using (almost) nothing but C,
there should be something fundamental in it. In language itself and in things
that surround it.

------
vinayms
I liked the article. Many things mentioned there are inconvenient truths.

Claiming that a particular high level language must be learnt in order to
understand how computers work is somewhat paradoxical - a hll is meant to
abstract details, so if it helps understand the internals then it can't be a
hll.

Also, the purpose of a programming language is not to teach how computer
works; its purpose is to make computers do what we want. Its the steering
wheel, gear stick and pedals of a car. If you want to know how a car works,
you have to get out and look under the hood or slide underneath. Driving a
manual transmission doesn't necessarily give any insight into workings of a
car, except may be knowing that something like a clutch exists that is needed
to be used to shift gears. Its your curiosity that makes you learn.

------
nwmcsween
Technical nit: POSIX defines CHAR_BIT == 8 and on hardware where CHAR_BIT can
be 16, 32 the alternative is to generate a lot of assembly (or just not work)
to access 8 bits at a time.

In my opinion everyone should learn assembly in combination with a simple
language to understand how a computer works.

~~~
steveklabnik
Technical nit on technical nit: I made no claims about POSIX in this post.
You're 100% right though!

------
jacquesm
No. But it helps, as will learning any other language, including assembler and
if you have the time for it some electronics.

[https://www.nand2tetris.org/](https://www.nand2tetris.org/)

Is still one of the best resources if you want to go from the ground up.

------
wzdd
I'm afraid I found this a rather confused discussion of whether C will teach
you "how the computer works". Yes, C is designed for an abstract machine --
all programming languages are, with the arguable exception of assembly
language. Even with a language that perfectly modelled your CPU, you wouldn't
learn "how the computer works" because the programming model presented by the
CPU is itself an abstraction.* What's more important to my mind are the two
major divergences between the hardware C was designed for and modern hardware,
viz:

1\. Massive changes to relative speeds of the memory hierarchy, and in
particular the increasing divergence between near-CPU caches and system RAM.
Nowadays it's often cheaper to recompute something rather than store it in
memory. C is reasonable about letting you reason about this sort of thing,
though, and

2\. A major and increasing focus on parallelism for performance, which C is
really bad at. Taking advantage of parallelism in C, particularly heterogenous
parallelism such as clusters and GPUs, is a real pain. Classic example: the
PS3 "Emotion Engine"; more modern example: graphics and physics coprocessors
and CUDA. At the other end of the spectrum we have languages like Futhark
where your code will be equally happy on a CPU, GPU, or both.

Compared with these major issues the differing size of char, say, is kinda
irrelevant. Yes, CHAR_BIT >= 8 is not how the computer works, but that's not
really a fundamental issue in the sense that -- well, in the sense that you
can quite easily learn C thinking that CHAR_BIT always equals 8, and then
update your knowledge relatively quickly. It's a small and modular piece of
knowledge in the same way that, for example, updating your mental model of
programming to include parallelism is not.

* This seems like a trivial point, but it's not -- actually understanding what's happening with your program may involve understanding DMA, the way buses are organised on your system, the way IRQs work, cycle timings, and on and on. The best you can hope for at the PL level is to have a reasonable high-level idea of what the CPU (or at least a single core) is actually doing, and C is still relatively good at this.

~~~
steveklabnik
I picked the CHAR_BIT example because it's very simple. I agree that it is not
a major impediment to writing correct C today, most of the time.

One of my follow-up articles is precisely about the memory hierarchy...

------
PopeDotNinja
Great article!

I tried getting through K&R C last tear, but never made it past chapter 2 or
3. I had thought C would teach me about how about all things computer-y below
the higher level language I was using. What I really learned was how little C
actually does for the things I wanted to understand. I wanted to learn about
sockets, file descriptors, protocols, process forking, writing drivers, etc. I
had little interest in learning about how many landmines there were to avoid
in writing good C, and I had little interest in reimplementing features in
some higher level language. When I say little interest, I mean I came to
understand my priorities were more about learning how Linux works. I REALLY
wanted to understand is how Linux does its thing in whatever language(s) it
was written in. At the time, I pivoted to deeply reading about Linux concepts
in How Linux Works, and I really enjoyed it. [1]

So for me, starting to learn C opened the door to a whole bunch of cool paths
to follow. I may or may not get back to learning C itself at some point, but
right now I'm having fun learning other things.

[1] [https://nostarch.com/howlinuxworks2](https://nostarch.com/howlinuxworks2)

------
CodeSheikh
This is a wrong statement on so many levels: "By learning C, you can learn how
computers work". I am glad you did write an article but honestly you did not
have to go to this length to explain your case. Anyone who says that learning
C is a prereq. for understanding Computer architecture then my friend the
advice is not coming from a right place. One reason I can think of is why
author got confused is because a lot of freshman courses use C programming
language in order to explain the computer architecture.

e.g.
[http://www.ece.utexas.edu/undergraduate/courses/306](http://www.ece.utexas.edu/undergraduate/courses/306)
use this textbook: [https://www.amazon.com/Introduction-Computing-Systems-
Gates-...](https://www.amazon.com/Introduction-Computing-Systems-Gates-
Beyond/dp/0072467509)

This course starts with assembly language and then use C as the first
programming language in order to understand computer architecture. C is the
de-facto language when it comes to teaching embedded systems (think limited HW
resources like memory etc).

Learn Assembly to learn how computer works!

~~~
xfer
You don't need to learn assembly to learn how computer works(if you mean comp.
arch). Assembly is already very high level language and hides most of the
things that you learn by actually studying computer architecture. Reading the
intel's first couple volumes of manual is a good idea, if you don't know where
to start.

------
vngzs
This reminds me of _C is not a low level language_ [1] (HN discussion [2]).

From [1]:

> Your computer is not a fast PDP-11.

[1]
[https://queue.acm.org/detail.cfm?id=3212479](https://queue.acm.org/detail.cfm?id=3212479)

[2]
[https://news.ycombinator.com/item?id=16967675](https://news.ycombinator.com/item?id=16967675)

------
Annatar
"In 1972, on a PDP-11, they wrote the first C compiler, and simultaneously re-
wrote UNIX in C. Initially, portability wasn’t the actual goal, but C did
resonate with a lot of people, and C compilers were ported to other systems."

The fathers of UNIX explicitly said in one of the interviews that they
invented C so that they could make UNIX portable.

~~~
steveklabnik
So, I had heard that as well, but
[http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.138...](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.138.35&rep=rep1&type=pdf),
co-authored by Dennis Ritchie, says

> C was developed for the PDP-11 on the UNIX system in 1972. Portability was
> not an explicit goal in its design, even though limitations in the
> underlying machine model assumed by the predecessors of C made us well aware
> that not all machines were the same [2].

So that's what I went with.

------
bordercases
It's worth learning as the prima facie example of a high-level language on top
of the register memory model.

[http://canonical.org/~kragen/memory-
models/](http://canonical.org/~kragen/memory-models/)

Its perceived close relationship to the machine is precisely due to the fact
that it exposes pointers & does not handle its own memory allocation with a
garbage collector, both of which are direct consequences of the register
model.

Why not Assembly? Because it's not a language that encodes propositions first
class, and that's the grounding for structured/procedural programming. The
majority of software requires some structured element and so regressing
towards the assembly model, where abstractions are implicit, doesn't get new
programmers on the path to understanding how to write nearly all software.

------
Bekwnn
I don't think C/C++ teach you how the computer works, but they do teach you
how memory works, something which is almost entirely abstracted away with most
other languages.

You'll avoid some really bad decisions that you might otherwise blindly walk
into without that knowledge even in higher-level languages.

------
otachack
One of my favorite classes in college was Computer Architecture 1 and 2, which
encompassed logic gates, Boolean logic, and progressed to assembly language
usage. C was covered in other classes and was a natural next step from
Architecture. I credit those classes a ton for my lower level knowledge.

~~~
thrower123
I agree 100%, that was far and away the most valuable single class I took in
college. We used the Tannenbaum book and a digital logic simulator, and worked
up from basic gates into basic components (e.g. flip-flops and muxes, etc),
then combined those into bigger components (ALU, registers, SRAM, etc),
combined those into a toy 8-bit CPU, implemented cache and main memory, wrote
an assembler that converted a small subset of x86 instructions into the
opcodes for that toy CPU, then a limited dialect of C that generated that
assembly code, that we converted to opcodes, that we ran on that simulated toy
CPU.

It was probably the hardest course of my entire degree, and I ended up taking
it twice, but at the end of the second go-round, computers were no longer a
magic black box.

~~~
vram22
>It was probably the hardest course of my entire degree, and I ended up taking
it twice,

Interesting. Did you study at a US univ.? I have not heard of being able to
take the same course twice (except via, say, failing a year of the degree and
having to repeat the whole year). I did hear that US universities are more
flexible in some ways than, say, Indian ones, whose rules are probably based
on British ones (maybe ones from much before); e.g. being able to mix and
match courses, take longer than the standard 4 years for a degree, etc. I have
American friends and relatives who told me that. But didn't know one could
repeat a course. Seems like a useful feature.

~~~
thrower123
I wasn't really ready to take it the first time (freshman spring...), and
dropped it before the halfway point so I wouldn't take the hit on my GPA.

US colleges are all over the map with regards to how flexible they are about
course order, and even between different degree programs at the same school.
My alma mater had a somewhat weird trimester schedule and was relatively
flexible about offering sections of courses year round - they had a bit of a
housing shortage that made it impossible to house all the undergrads on campus
at the same time.

~~~
vram22
Got it now, thanks. Interesting about US colleges. I was under the (wrong)
impression earlier that all of them had that sort of flexibility about course
order and more so, about degree duration. It was based on hearing things from
a few friends. I guess I extrapolated, incorrectly.

------
intrasight
I learned how computers work by breadboarding CPUs from smaller logic
components.

Learning how commercial CPUs work I did by programming the 6502 and 6800 using
machine language and assembly language.

For learning how to write good assembly language, I compiled C into assembly
language. So C definitely helped me learn.

------
shittyadmin
If you want to learn how it works, it's a lot better to start at the lower
level in my opinion. Reverse engineer something you use every day, get an
achievable goal, maybe fire up x64dbg, go out and find some malware, make an
advanced cheat for your favorite game or crack something.

You'll learn what code looks like to the CPU, you'll get a feeling for how OOP
works inside that box (things like the "this" pointer, vtables), what
structures look like in memory, how dynamic linking works vs static linking by
actually looking at what that code looks like in an executable. And you'll
have something to show for it - something to prove you've acquired enough
knowledge to achieve goals.

------
agentultra
Good article.

If your goal is to learn how computers work I think an education in C will
follow a long, twisting pedagogy. It is more likely you will encounter
subjects that will teach you how computers work by using C. However you can
also happily program in C without understanding how memory is managed beyond
developing an intuition for using malloc, free, and pointers.

If you want to be more direct why not build a computer from scratch [0]? Or
take a course on operating systems [1]?

[0] [https://www.nand2tetris.org/](https://www.nand2tetris.org/) [1]
[https://wiki.osdev.org/Getting_Started](https://wiki.osdev.org/Getting_Started)

------
droithomme
I agree with his own summary of his own article: "[T]his idea is not
inherently wrong, but does come with some caveats. As long as you keep those
caveats in mind, I think this can be a viable strategy for learning new and
important things."

If one really wants to know how the computer works, the course "Build a Modern
Computer from First Principles: From Nand to Tetris" is excellent. You start
with basic gates, build logic devices, build a CPU, build a computer, write an
assembler, write a virtual machine, and finally write a compiler and then
implement whatever game you like in it.

The class, astonishingly, has no prerequisites, and has been completed by
people from 10 to 80.

------
andrewla
Discussing this with other tech folk in my circle of a certain age, I'm
actually in favor of learning BASIC (old-school line-number basic) to learn
how a computer works, because the conceptual model is not dissimilar to
assembly language.

Concepts of structured programming arise from noting patterns commonly used --
you start using GOSUB instead of GOTO; you tend to make your loops very
strictly nested because otherwise the code becomes unmaintainable, you find
yourself writing data structures to manage related records, etc. From there,
the transition to structured programming becomes very natural -- just
formalizing some concepts that you've already internalized.

------
samontar
Wrote lots of C once. It’s useful to me in the sense that I comfortably reach
for language interfaces to the C FFI or something like JNI but I think
learning a functional language contributed more to my development than C ever
did.

------
bsder
The main thing that C does is force you to use "pointers".

And "pointers" force you to learn "indirection".

And, "indirection" is like "recursion" in being one of the absolute core
concepts you _MUST_ learn in order to program well.

Rust is actually somewhat obscuring "indirection" a bit because instead of a
single thing to keep track of you now have two things--(base, index)--which
you can think of as a single unit but newbies are going to get confused.
Because of the immutability, people are throwing around data structures of
indices like in very old FORTRAN code common-block programming.

------
FrozenVoid
Looking at asm dumps of (non-optimized) C code is a great learning tool to
understand "how the computer works"(see
[https://godbolt.org/](https://godbolt.org/) ) . C by itself is not, but
building up knowledge from assembler doesn't touch how actual(C-level)
software works. If you study only assembler there is a large information gap
from primitive opcodes to software functions and general architectural
features that opcodes only briefly interact with, to 'get' overall design you
have to study C/C++ implementations.

------
Koshkin
Learning C has practical benefits, but gaining an understanding of how
computers work is not one of them.

Learning an assembler would be more helpful in this regard, but in my opinion
one might want to start with a very well-written article by Mark Smotherman:
[https://people.cs.clemson.edu/~mark/uprog.html](https://people.cs.clemson.edu/~mark/uprog.html)

If you want to _see_ how a CPU works, I highly recommend visiting
[http://www.visual6502.org/](http://www.visual6502.org/)

~~~
mrfredward
While I wouldn't suggest anyone learn C for the explicit purpose of learning
how computers work, being familiar with pointers and memory allocation can go
a long way.

Novice unity programmers struggle to understand why using classes (reference
type in c#) in a game loop causes performance issues, but structs (value type
in c#) are not as costly. For someone who has called malloc before, this isn't
too hard to grasp, but before someone who sticks to garbage collected
languages, it seems bizarre and arbitrary.

Of course memory allocation is an OS function, so this isn't "understanding
the hardware," but it is a much deeper understanding of computers than not
knowing what happens when you create an object.

------
sam0x17
You should learn C to learn what every language is loosely based on and what
unrestricted memory access looks like. It's the programming equivalent of
learning Latin. It isn't required, but you will be better at pretty much
everything you do involving modern English, Spanish, French, etc., as you will
understand where things came from. It's even more useful though, as in the
programming language world pretty much everything evolved out of C, whereas in
linguistics you also have the Asian languages, Greek, etc.,

~~~
pjmlp
> It's even more useful though, as in the programming language world pretty
> much everything evolved out of C

Not really, there was a world of computing outside AT&T walls.

Lots of papers and computing manuals are available online for those that care
about the actual history of computing.

~~~
sam0x17
This is a genuine question -- are there non-C-family systems languages from
that period (as in languages that allow low level memory manipulation and
assembly embedding)? I have never heard of or seen any.

~~~
pjmlp
Lots of them.

Burroughs designed ESPOL in 1961, improved into NEWP, still being sold by
Unisys as ClearPath MCP.

IBM did all their RISC research with PL/8, before creating Aix for their RISC
systems thus adopting C instead.

IBM i and z mainframes made use of PL/S, C and C++ only came later into the
picture as the languages got industry adoption.

Xerox PARC initially used BCPL, but quickly followed up with Mesa, then
Mesa/Cedar.

Wirth was inspired by Mesa to create Modula-2 and then by Mesa/Cedar to create
Oberon.

Mesa/Cedar designers went to Olivetti and created Modula-2+ and Modula-3.

Apple wrote Lisa and initial versions of MacOS in a mix of Pascal and
Assembly.

VAX used BLISS to develop VMS.

PL/I was used by multiple companies, not only by MIT to write Multics.

This is just a very brief overview.

As for Assembly embedding, it is a language extension not part of ISO C, and
was quite common in many languages during the 70 and 80's even some BASIC
interpreters had it, like on Acorn computers.

------
davemp
I think that in order have true mastery of C, one must understand _why_
certain things are undefined. For example signed integer overflow is
undefined. Why? Well different architectures handle overflow in their own ways
(saturating vs modulo arithmetic, twos compliment). C can a great way to learn
about computers in general vs assembly on a specific architecture--unless you
approach C undefined behavior as most undergrads do and proclaim how arbitrary
the silly C language spec is.

------
pmarin
If you really want to know "How the computer works" read Computer Organization
And Design by Patterson and Hennessy.

It is a great example of how to use the C language as a teaching tool.

~~~
steveklabnik
This book is fantastic, for sure.

Learning assembly and/or microarchitecture is another great way to get
"knowing how the computer works," and is part of what I was hinting at at the
end of the post when I said that C isn't fundamental here.

------
justinhj
I learned assembly language before c which I suspect helped understand
pointers. There are plenty of fun ways to learn assembly language. The game
Shenzhen IO is one.

~~~
0xfaded
I started with MASM because C cost money. By that I mean "Visual C++", and of
course 12 year old me had no idea about GNU. I think I got through the MASM
tutorials to the point of interacting with the Win32 api before finally
figuring out I was doing it wrong.

------
DroidX86
For someone coming from a high(er)-level programming language like Python -
who wants to learn more about how computers work - I think it would be a
useful stepping to learn C. C introduces some of the concepts and techniques
that you need to understand Operating Systems & lower level things like that.

------
forkandwait
I taught myself atmega328 C programming in a breadboard blinking led context
last year and thought that was a wonderful learning experience. I sort of knew
C, and didn't get much pointer practice, but it was great nevertheless.

Also, Stevens Advanced Programming in the Unix Environment is a great book to
work through.

------
pcunite
I don't know if I'm in the _should_ camp but beneficial? Yes, since so many
things in programming might be taken for granted without seeing how things are
done another level down. But then you could keep going all the way down, so
who decides how far a student should go?

------
winrid
C will teach you how computers _used_ to work. Nowadays there are lots of
hacks to make C code from decades ago still work but the fact is the hardware
and how it manages memory is completely different.

Anyway, you'll learn some useful fundamentals regardless.

------
kazinator
An aspect of "how the computer works" comes into play when the C program goes
so wrong that you debug it at the level of examining disassembled code,
registers and memory contents.

------
dman
One thing I like about C is that it is a small language hence making it very
approachable to newcomers. Other systems languages (C++ / Rust) have much
longer roads to being productive.

~~~
lmm
What does "small language" even mean here? C has far more keywords, built in
control flow constructs, and other special case builtins than, say, OCaml, so
I'd argue it's actually a very large language. C's standard library is small
but only because it's missing huge amounts of functionality (which might be
acceptable in a language with a good dependency manager, but C doesn't have
that).

~~~
dman
Here is my (arbitrary) definition of small 1\. Have a concise crisp book
capturing the language

2\. Give you a (false) sense of being productive in a couple of days (maybe a
week)

3\. Limited syntactic extensibility / metaprogrammability so that you dont run
into extremely "clever" online discussions.

4\. Ability to be productive without learning a new build tool / ide.

5\. Ability to be productive without requiring auto completion.

~~~
lmm
I don't see how C passes 2 (it's notorious as a language where it takes 200
lines to make a HTTP request) or 3 (it has arbitrary textual macros, so
there's all sorts of "cleverness" e.g. GObject). And I'd regard 4 and 5 as
false as even C-with-IDE is less productive than e.g., to return to the same
example, OCaml-without-IDE.

~~~
dman
I am willing to concede that OCaml is a small language (Having not used it, I
dont have any evidence for or against).

As for C, I was looking at small purely from the views of a new comer who is
using a book to come upto speed with a language. You can write the programs
from c books as is in a simple text editor and just compile / run them. C
Books dont spend a huge amount of focus on arbitrary textual macros so in
practice you do not run into macros as a newcomer.

~~~
lmm
I'm not trying to push OCaml specifically, I'm just using it as an example of
a "normal" language. I'd consider most languages (e.g. Python, TCL, Ruby,
Java, Lisp, Haskell...) to be "smaller" than C; indeed the only languages I
can think of that feel "bigger" are C++ and Perl.

------
diminoten
I just have so many problems working with the former C programmers on my team
as a Python dev. They're constantly worried about memory management, they're
not very concerned about architecture and don't make full use of the Python
language, they're of the "if it works, ship it" mentality, and they're
constantly writing code that violates my tastes.

I can write a C program, but I am not in any way a C ninja. I sometimes wonder
if having to write C to pay for the food on your table teaches you bad habits
when trying to bring that experience up a layer (or three). Maybe not, I don't
know. Am I alone in this thinking?

~~~
sys_64738
Python has scalability issues which are solved by calling into C libraries.
What does that tell you about whom should be listening to who?

There are many reasons C programmers are experts in their craft while python
programmers are 10 a penny.

~~~
diminoten
My point was that C programmers _aren 't_ experts at their craft, just experts
at C, and all the habits that were possibly good in C carry over, making them
less than good at Python.

Also, I think your comment is a great example of the mentality that C
programmers I've worked with brought to the table (partially because it's true
for C but untrue for other language, particularly python); that if you can
write code that runs, you are "good", that "runnable code that solves the
problem" is the end goal.

Finally, there are most certainly _not_ a glut of python developers. Certainly
many people who write python scripts, but far fewer people who are python
developers. There's a difference, and I get the sense that's lost on some C
programmers.

~~~
sys_64738
C is their craft and they're experts so what's not to like?

I'm not sure what this 'good enough' angle is and why you think it's specific
to C. It's an issue to other language more than C as those other languages are
often used by shops without proper software engineering practices in place.
All the places I've worked which use C enforce those SW practices but it's not
clear to me that the same could be said for other languages.

If anything, one of python's downfalls is its desire to do too much complexity
in lambda or list comprehension statements. Those areas that cause you to stop
and have to think about the code are weaknesses of python but every novice
tries to do it by copying and pasting from stackoverflow.

So I think you've got the position flipped, IMO.

~~~
diminoten
You keep talking about Python's problems, and I'm trying to talk about the
habits I've noticed C programmers form and are rewarded for, but are bad for
Python development.

There are literally no habits in your mind that could be good in C but bad in
Python?

~~~
sys_64738
As I am a veteran C programmer with decades of experience with several years
of straight python now, what I see is a people who think they understand how
to write complex software but don't grasp the fundamentals. This isn't
explicitly a C V Python programmer discussion so much as exposing that Python
insulates you from the peculiarities of things like debugging complex
problems, writing robust code, good software practices, understanding
libraries, scalability, parallel programming. The list is endless for the
weaknesses of python and the developer who is comfortable there, which is fine
given Python is a scripting language at heart.

~~~
diminoten
> which is fine given Python is a scripting language at heart

This is _exactly_ the problem, and it's actually kind of rare to get an
example of what you're talking about to show up in the very comments you're
using to explain the problem! Very validating, that is.

For the other folks reading this, imagine trying to work with someone who
thinks these things. Imagine how unwilling to change their bad habits they'll
be, since they think that because they're good at C means they're good at
writing software. Any trouble they run into is the language's fault, not
theirs!

~~~
sys_64738
Are you claiming Python _isn't_ a scripting language? If it isn't then what
the heck is it?!?

What I find generally from Python developers who have lived an breathed the
language for many years is that they accept there are fundamental failings and
acknowledge the better means to workaround those.

In reading back through your comments, it seems you are blinkered by those
with the benefit of working in other languages. In particular, you seem to
have hang ups about C which are not clear why.

Perhaps you'd care to explain those.

~~~
diminoten
Python is an object oriented programming language, not a "scripting" language.
"Scripting" implies writing "scripts" (e.g. #!/usr/bin/python), but that's not
it's only (or primary) use case, and in fact creates the problems I've been
talking about above.

Python the language has plenty of flaws. This conversation was never about
Python however, it was about the bad habits C programmers bring to Python
enterprise development, and how I struggle to work with C programmers because
of those good-in-c-but-bad-in-python habits. I was curious if anyone else
experienced these problems, and then you arrived and became a perfect example
of the attitudes I encounter regularly from C programmers working in Python.

I have no problems with C, it's _obviously_ one of the most (if not the most)
important programming languages of our time. My problem is with the habits
some C programmers have brought to my Python projects.

~~~
sys_64738
It's clear you've had an unpleasant experience in the past. It's sounds like
maybe you were burned in that experience and dumped the blame on a particular
group.

------
chaoticmass
Learning C really helped demystify a lot of what used to seem like magic that
higher level languages do, like memory management for starters.

~~~
pjmlp
You mean like this?

    
    
        var
          ptr : pointer to integer;
    
        begin
          New(ptr);
          ptr^ = 23;
          Dispose(ptr)
        end;

~~~
chaoticmass
What is this?

------
LiterallyDoge
I think every nail has its hammer and C is a good tool for certain things.
Other tools are also good for certain other things.

------
loukrazy
For me it was the opposite. Once I learned computer architecture then I
finally understood memory allocation and pointers in C

------
martin1975
sure, if you want to learn how von neumann style architecture inspired
programming works.. but some folk I know would curse it and stay you should
start with Scheme or perhaps Haskell if you truly want to learn programming
:). The truth is probably somewhere inbetween.

------
just_myles
Nah i'm good on that. If it is not necessary for what you are doing then, no.

------
neuralzen
NAND2Tetris would be a much better way to learn how computers work, imo.

------
cbsmith
It teaches you how PDP-11 assembly worked back in the day.

------
SlowRobotAhead
C... IDK. Maybe.

But, pointers yes definitely. Even some assembly. You really should understand
how things like .size or lengh() aren’t “free” and how they work.

~~~
steveklabnik
Funny enough, pointers are a great example of an abstraction provided by C
that people assume is isomorphic to hardware, when they don't:
[https://blog.regehr.org/archives/1621](https://blog.regehr.org/archives/1621)

That said, I 10000% agree that pointers are a thing that is great to learn.

> You really should understand how things like .size or lengh() aren’t “free”
> and how they work.

You're talking about strings here, right? This is not true in all languages,
but certainly is true that these are not free in C :)

~~~
wyldfire
> You're talking about strings here, right? This is not true in all languages,
> but certainly is for C :)

It costs O(1) in memory to delimit strings with a null terminator and O(n) in
time to execute strnlen(), right? It's not free. Though, if you're lucky your
string is in rodata and the compiler can calculate the sizeof() in advance,
this is very-nearly-free.

EDIT: misunderstanding, disregard

~~~
steveklabnik
Only if your strings are null terminated; many languages do not use null
termination for various reasons, and that's one of them. "Pascal strings"
being the nickname, given how old this idea is, and given it was a direct
competitor to C at the time.

~~~
wyldfire
> > You really should understand how things like .size or lengh() aren’t
> “free” and how they work.

> You're talking about strings here, right? This is not true in all languages,
> but certainly is for C :)

I realize now that I misread this. It sounded as if you were saying '...but it
is [free] for C', and on re-read I now understand that's not what you said at
all.

~~~
steveklabnik
Ah, no worries! I've edited it to make it more clear, hopefully.

------
iliaznk
Isn't the answer obvious? The inventors of C did apparently know how computer
worked before there even was C.

~~~
Ar-Curunir
not sure if you're trying to be facetious, but no, the answer is not obvious:
it could have been the case that the creators of C decided to make it a
faithful, thin abstraction aover the hardware of the computers they were
using. In that case, learning how C works would tell you a lot about how that
specific computer worked.

One point made in the article is that a) C wasn't really developed that way,
and b) computers have changed considerably since then, so even the above was
true, it no longer is true now.

------
agumonkey
Learn Forth

------
rustcharm
Why not assembly language? Learn a simple one in a simulator (like 6502 or
8080).

~~~
fixermark
There's some charm to it, but in general, assembly languages is _far_ too low
level to solve much of any interesting problem (unless the problems one is
interested in are in the domain of "How do I reduce this compiler to an
assembly implementation so I can use this language on a specific piece of
hardware?"). So you may run into some real challenges retaining student
interest.

But I think it's a good suggestion; I certainly learned a bit from learning
Apple's assembly on the ][c that I hadn't encountered elsewhere.

------
qubax
C and systems programming to learn how software works.

Paraphrasing Churchill:

I would make them all learn [python|java|ruby|etc]: and then I would let the
clever ones learn C as an honor, and assembly [MASM|NASM|GAS|etc] as a treat.

An educated person should know a bit of latin and ancient greek. And educated
computer science person should know C and assembly.

------
endorphone
Is this not a strawman? I've never seen anyone, ever, say that C is "how the
computer works". Searching for references (since the author provides pseudo-
quotes to refute that they seem to have invented) finds shockingly few making
such a claim.

~~~
sinistersnare
I know its anecdotal, but I hear the phrase all of the time. People who have
only ever used higher-level languages, and want to learn more about computers.
The say that they would rather learn C over C++, Rust, etc. because C is 'how
computers work'.

------
drblast
If you don't know C and it would be the lowest-level language you would
otherwise know, you should learn it.

Same with assembly.

Same with writing your own operating system in some combination of C and
assembler. At that point you're really targeting the hardware and getting a
better idea about how everything works.

There's no question as to the value of knowing these things. Should learning
them be a top priority for you? Depends on you.

------
HankB99
I stopped reading when I got to "C also operates inside of a virtual machine."

Is the author confusing virtual memory with virtual machine? Perhaps they're
referring to the way a high level language abstracts away the details of the
H/W. I didn't care enough to read on.

That said, I learned the most about "how a computer works" in a class that
taught Intel 8080 assembler on a system running CP/M. (Technically it was MP/M
but there was only a single console.) The instructor spent a lot of time
explaining what registers were, accumulators, stack pointer, addressing RAM
and so on. That's why I learned about H/W details, not because I focused on a
particular language.

I thought the entire reason for C was to avoid having to deal directly with
the H/W (though it can be done.)

~~~
steveklabnik
> I stopped reading when I got to "C also operates inside of a virtual
> machine."

Then you missed my actual explanation and description, which is described in
the rest of the post. Since others are also apparently confused, I'll re-state
it here.

The C language is not defined in terms of hardware. The C language is defined
in terms of an "abstract machine." This machine, being abstract, does not
exist. "Virtual" and "abstract" are terms that are broadly speaking synonyms
in general usage.

The spec even defines this in terms of an "execution environment" which can be
"freestanding" or "hosted", and most C programs are, in the spec's language,
running in the execution environment of C's hosted, abstract machine.

The rest of the post is exploring how this is conceptually the same, but
different in details, than something like the JVM, which is what many people
think of when they hear "virtual machine." As mentioned elsewhere in the
thread, the sense of "virtual machine" that C uses is where LLVM gets its name
from, yet people found that confusing enough that they changed the name.

This comment is also a re-statement of my point, maybe it helps:
[https://news.ycombinator.com/item?id=18123642](https://news.ycombinator.com/item?id=18123642)

You may also argue that the abstract machine is practically insignificant. I
disagree, but that's what my follow-up posts are about, so this post doesn't
address this question directly at all.

Does that help?

~~~
fmihaila
> Then you missed my actual explanation and description, which is described in
> the rest of the post. Since others are also apparently confused, I'll re-
> state it here.

I think the confusion stems from using the word "operates", which suggests a
VM that exists at runtime:

> There’s just one problem with this: C also operates inside of a virtual
> machine.

I agree with the sibling comment that in this context the term abstract
machine would be better. Also, saying "C is defined in terms of an abstract
machine" instead of "operates" might be better.

~~~
steveklabnik
In the language of the spec, it does “operate” inside the machine. Well, the
spec says “execute” but that’s even more likely to be confused with a runtime
thing.

Additionally, and this is something I really didn’t get into, languages aren’t
inherently compiled or interpreted. You could have a C interpreter.

~~~
fmihaila
> Additionally, and this is something I really didn’t get into, languages
> aren’t inherently compiled or interpreted. You could have a C interpreter.

Of course; I was only addressing the confusion that other posters mentioned.
While one could have a C interpreter (or any interpreter), for the purposes of
the article this seems to be only a marginal point. In practice, C is (almost)
never run that way, but your choice of words could suggest a parallel between
the C abstract machine and the Ruby or Java VMs, and I think the way usual C
implementations differ from those is more informative than how they are alike.

You do explain later that the C abstract machine is a compile-time construct,
but many people read selectively and react immediately, as evidenced by
several posts on this page.

