C Part I · Module 1 · Understanding C and How Programs Work
Why C Exists and Where It Is Still Used
Chapter 1.1 left one question open. If C is not assembly language, why does it expose so much more of the machine than most languages do? The answer is not an accident of age. C was the response to a specific engineering squeeze: software written in terms of one processor could not be moved to another, but the software that manages a computer still has to reason about that computer in detail. Neither extreme worked. This chapter follows that problem to its resolution, uses Unix as the evidence rather than the subject, and then shows why the same squeeze still exists — which is why C is still underneath operating systems, firmware, language runtimes, and the software that talks to new silicon. By the end, C's more awkward design choices should read as decisions with a reason behind them.
Foundation10 min readCPart IFundamentalsMental Model
Part I · Module 1 · Chapter 1.2 · Why C Exists and Where It Is Still Used
1. Software That Could Not Be Moved
Imagine spending a year building a large program. It works. Then your organisation buys a different kind of computer, and you discover that almost none of your year's work can be carried across. Not adjusted — rewritten.
That was an ordinary situation once, and the reason was the language most serious software was written in.
Chapter 1.1's comparison table named assembly language: text in which each line names an operation that one particular family of processors can perform. Writing in assembly means writing in the vocabulary of a specific machine. That has a real advantage — you are saying exactly what you want done — and one large consequence: a different family of processors has a different vocabulary, so the text does not carry over. There is nothing to adjust, because nothing about it was written in general terms in the first place.
The word for what such software lacks is portability: the ability to move a program to a different kind of machine without rebuilding it from scratch. Assembly source has very little of it: it carries over to machines that share the same vocabulary, and essentially not at all to machines that do not.
For a small program this is an annoyance. For a large body of software it is a wall. Every new machine means starting again, and every improvement you make afterwards has to be made separately in each version you now maintain.
2. Why You Could Not Simply Stop Caring About the Machine
There were already languages by then that hid the machine almost completely. So the obvious escape was to write everything in one of those, and stop thinking about processors at all.
That works for a great deal of software. It did not work for the kind at issue here.
Some programs exist mainly to make other programs possible, or to decide how a computer's resources get used. An operating system is the clearest example: it is the software that decides which program runs, hands out memory, and stands between everything else and the machine's devices. Work of that kind is usually called system software, and writing it is systems programming.
System software cannot be indifferent to the machine, because managing the machine is its job. It has to reason about how much space something occupies, where in memory a particular thing sits, and exactly what bytes get handed to a device. A language that hides all of that from you has not solved your problem — it has removed the vocabulary you needed to describe it.
So the two obvious options both failed, in opposite directions:
- Write in the machine's own vocabulary, and the work cannot be moved.
- Write in a language that hides the machine, and the work cannot be done.
C exists in the gap between those two failures. Everything that follows in this chapter — and a good deal of what looks strange about C later — comes from that squeeze.
3. The Path to C
The resolution came out of Bell Labs in the early 1970s, alongside an operating system called Unix. The two developed together, and it is worth being careful about the order, because it is often stated backwards: Unix existed first, written in assembly, and C grew up in the environment of people trying to make that work sustainable.
C did not appear from nothing. It descends from a language called BCPL, by way of a smaller language called B, written by Ken Thompson. Dennis Ritchie developed C from B.
One detail from that lineage matters more than the rest, because it explains something you will meet constantly. B, following BCPL, was typeless: it treated memory as a uniform run of machine words, and a value was simply a word. That model was workable while the hardware matched it. It became awkward when the machines in use addressed individual bytes — smaller units than a word — and when programs needed to describe data more precisely than "a word": a character, a small number, a group of related values stored together.
C's answer was to give values types. A type says what kind of value you are dealing with — and from that follow both how it is stored and what operations on it mean. That single decision is why so much of C is concerned with representation, and why later modules in this curriculum spend so long on it. It was not added for tidiness. It was added because the alternative could not describe the data real programs had to handle.
4. Unix Made the Case
An idea in a language design is a claim. Unix is what turned it into evidence.
Unix was first written in assembly. In 1973 its kernel — the core of the operating system — was rewritten in C. That is the moment worth noticing: a substantial, genuinely demanding piece of system software, of exactly the kind argued to require machine-specific code, was instead expressed in a language that was not tied to one processor.
Later in the decade the system was moved to a very different machine, and it went. That is the demonstration. But the detail underneath it is the part most accounts leave out, and it is the part you should keep:
5. The Trade-Off C Chose
With the problem and the evidence in place, C's design reads as a set of decisions rather than a set of quirks.
Above the machine, C gives you ordinary ways to express the things every program needs — arithmetic, decisions, repetition, named pieces of work, grouped data — none of which you write in any particular processor's vocabulary. That part is what makes the logic movable.
Still visible, deliberately, are the things system software has to reason about: how much space a value occupies, how the bits inside it are interpreted, where something sits in memory, and how a group of values is laid out. Many languages answer those questions on your behalf, and they differ a great deal in how much they hide. C leaves them to you, because for the work it was built for, they are the work.
Being a relatively small language served the same goal from the other side. A language with a modest set of features could realistically be implemented on many different machines — which is a precondition for portability, not a separate virtue.
That is the whole trade-off. You do not get to ignore the machine, and in exchange you are not locked to one.
6. Why the Trade-Off Still Holds
"C is old but fast" is the usual explanation for C's survival, and it explains almost nothing. Several more specific things keep it in place, and they reinforce each other:
- An enormous amount of working software is already written in it. Operating systems, language runtimes and foundational libraries that a great deal of other software sits on top of. Software that works and is understood is rarely rewritten without a reason.
- Compilers exist for nearly every processor anyone builds. A new chip almost always gets a C compiler early, because so much existing software expects one.
- C is the language other languages meet in. When a program written in one language needs to call code written in another, the shared convention they agree on is very often C's. That makes C the common denominator of the software world, independently of anyone's preference.
- It runs where there is very little underneath it. Many environments provide no operating system and very little memory. C can work in those conditions because it does not require much support beneath it.
- Data layout is something you work with rather than something hidden from you. That matters whenever something else has already decided the layout — a file format, a network protocol, another language, or a piece of hardware.
On speed, be precise rather than enthusiastic. C is common in performance-sensitive work because its compilers have been optimised for decades, because many of its operations correspond closely to things machines do directly, and because the programmer controls how data is represented and stored. None of that means a C program is automatically efficient. A poorly written C program is simply a poorly written program, and a well-written program in another language will often beat it.
7. Where C Still Appears
The same squeeze from §2 — needing to reason about the machine without being welded to one — still shows up in specific places.
| Where | The property of C that earns it a place |
|---|---|
| Operating systems and kernels | Must manage the machine's resources directly, and must run before any language's support machinery exists |
| Embedded systems and firmware | Often no operating system underneath and very little memory; C needs very little support beneath it |
| Compilers, language runtimes and foundational libraries | The layer other languages are built on and call into — which is the layer C is the common convention for |
| Semiconductor and SoC engineering | Software that has to agree, exactly, with hardware somebody else designed |
Two of those need a sentence of explanation.
An embedded system is a computer built into a larger product rather than sold as a computer — a router, a vehicle controller, a sensor, a washing machine. Firmware is the software that runs on it, often with no operating system beneath it at all.
The last row is why this curriculum exists. In semiconductor work, C turns up on both sides of the line between software and hardware: in the software that runs on a chip while it is being brought to life, in models written to say what a design should produce so that its actual output can be checked against them, and in the code that connects such models to the tools used to simulate hardware. What those uses have in common is an obligation to agree with hardware exactly — the same byte in the same place, meaning the same thing — and that is precisely the obligation C's design leaves in your hands rather than taking away. Part II of this curriculum is about that boundary in detail. None of its mechanisms belong here; the point for now is only that C's relevance to hardware engineering comes from the trade-off in §5, not from a separate fact about C.
8. What You Should Be Able to Say Now
Check yourself against these five.
- Software written in one processor's vocabulary cannot be moved to a different one — it has to be rewritten, not adjusted.
- System software cannot solve that by ignoring the machine, because reasoning about the machine is its job.
- C was built in the gap between those two failures, and its visible concern with representation comes from that, not from age.
- Unix showed the idea worked by being rewritten in C — but moving it to another machine still took real work and still needed some assembly.
- C persists because of a combination of existing software, near-universal compilers, its role as the convention languages meet in, its small support requirements, and the control it gives over data layout.
9. Exercise
Reasoning questions. All of them can be answered from this chapter and Chapter 1.1.
Exercise 1 — explain the cost. In your own words, why would an operating system written entirely in one processor's assembly language be so expensive to move to a different processor? Answer in two sentences, without using the word "portability".
Exercise 2 — name the squeeze. §2 describes two approaches that both failed, for opposite reasons. State each failure in one sentence, then say in one more sentence what C does differently.
Exercise 3 — reject an incomplete claim. Someone says: "C only survived because old companies never got round to replacing their code." Using §6, give two reasons that explanation is incomplete — at least one of which would still apply to a brand-new project starting today.
Exercise 4 — apply it. A small controller inside a piece of equipment has very little memory, no operating system, and must hand exact byte patterns to the hardware around it. Name two properties of C from this chapter that make it a plausible choice, and say which one would matter even if memory were plentiful.
10. Summary
- Software written in one machine's vocabulary cannot be moved. That was the original problem, and it grows more expensive the more software you have.
- System software could not escape by ignoring the machine, because managing the machine is what it does. Both obvious options failed, in opposite directions.
- C is the response to that squeeze: ordinary ways to express logic, with the machine-facing details — space, representation, layout — deliberately left visible to you.
- Unix turned the idea into evidence when its kernel was rewritten in C, while also showing the honest limit: moving software to a new machine remained real work, and some assembly stayed.
- The same trade-off still applies, which is why C sits underneath operating systems, firmware, language runtimes, and the software that has to agree exactly with hardware.
C has now existed for decades and been implemented for a very large number of machines by a great many different groups. That raises the question the next chapter has to answer before you write any code: when someone says "C", which set of rules do they actually mean — and how much of what your compiler accepts is the language, rather than that compiler's own idea of it?
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
