C addressed a concrete problem of the 1970s: how could an operating
system remain close enough to hardware for efficiency without being
almost entirely rewritten for every new computer?
Dennis Ritchie developed C at Bell Labs
in the context of Unix. The language did not try to conceal the machine.
It offered a small set of abstractions—types, functions, structures,
and pointers—that a compiler could translate efficiently across
architectures. That compromise explains both its spread and the memory
errors newer languages try to prevent.
Before C: BCPL and B
In the 1960s, systems software was still largely written in assembly.
Assembly controlled the processor precisely, but every architecture had
different instructions. Porting a program often meant rewriting much of
it.
Martin Richards designed BCPL in 1967 as a compact language for
compilers and systems. Ken Thompson
derived B from it for early Unix. B simplified work compared with
assembly, but its essentially typeless model was designed for
word-oriented machines.
The PDP-11 on which Unix evolved could address bytes and manipulate
several data sizes. Ritchie added types, improved arrays and structures,
and gradually transformed “New B” into C between 1971 and 1973.
A language shaped by Unix
C was not designed on paper and then applied to Unix. Language and
system evolved together. Kernel needs pushed C to represent addresses,
characters, memory blocks, and data structures with little overhead.
A pointer contains a data address. It can traverse an array,
construct a linked list, or access a device. Nothing automatically
guarantees that the address is valid. Code may read beyond an array, use
released storage, or interpret bytes as the wrong type.
The language also leaves details to implementations, including integer
sizes, byte order, and structure alignment. Portable code must rely on
guarantees defined by C rather than details observed on one machine.
Rewriting Unix changed the project’s reach
In 1973, most of the Unix kernel was rewritten in C. Hardware-specific
portions remained in assembly, but most of the system could be
recompiled for a new architecture.
This did not make every program automatically portable. Drivers and
hardware assumptions still needed adaptation. It nevertheless
demonstrated that a performant operating system could be expressed in a
higher-level language without becoming captive to one processor.
Unix then spread through universities with its source and tools.
Students learned the system and its primary language, wrote software,
and carried those practices into industry. C benefited from Unix
adoption; Unix became more adaptable through C.
A small syntax, an essential library
C has few built-in constructs. Input and output, string handling, and
dynamic allocation mainly come from libraries. This keeps the language
core compact and implementable in very different environments.
The standard library nevertheless establishes an essential contract. A
function such as fopen prevents every program from needing exact
system calls to open a file. Portability therefore rests on three
elements: language, compiler, and compatible library.
Compilation generally produces native code. C requires neither a virtual
machine nor garbage collection. Programmers control allocation
lifetimes, enabling predictable footprints while increasing
responsibility for resources.
From K&R C to an international standard
In 1978, Brian Kernighan and Dennis
Ritchie published The C Programming Language. It described C precisely
enough to become its de facto reference; that form is often called K&R
C.
As compilers multiplied, differences appeared. An ANSI committee began
defining a common standard in 1983. C89 formalized the language and
library, and ISO adopted it in 1990.
Later revisions cautiously added types, concurrency tools, and
clarifications. Evolution remains constrained by compatibility because
enormous codebases and system interfaces depend on historical C
behavior.
Why C influenced so many languages
C++ directly extended C with richer
abstractions. Objective-C combined it with a Smalltalk-inspired object
model. Java, JavaScript, C#, and Go borrowed much of its syntax without
retaining its memory model.
Syntactic influence should not obscure different contracts. Java
protects memory through a virtual machine. Rust uses a type system to
enforce lifetimes. Go uses garbage collection. Each responds differently
to the costs of C’s direct control.
C remains common ground in operating systems, libraries, and embedded
environments. Even memory-safe languages often communicate through APIs
defined in C.
The limits of trusting the programmer
The C standard defines undefined behavior: after certain rule
violations, no outcome is required. Compilers may optimize on the
assumption that these situations never occur. The freedom supports
performance but makes some errors difficult to predict.
Buffer overflows and lifetime mistakes have caused many vulnerabilities.
Compilers, static analysis, runtime protections, and coding rules reduce
but do not eliminate risk. C remains relevant where precise control and
broad compatibility are essential; that does not make it the default for
every application.
Why C still matters
C established an enduring abstraction layer between assembly and
high-level software. It describes operations close to hardware in
notation shared across architectures.
Its history shows that successful abstraction need not hide every cost;
it can make costs manageable. The price of that transparency is
discipline that C itself enforces only weakly and that teams, tools, and
standards must supply.
Timeline
- 1967: Martin Richards presents BCPL.
- 1969–1970: Ken Thompson develops B for early Unix tools.
- 1971–1972: Dennis Ritchie evolves B into C on the PDP-11.
- 1973: Most of the Unix kernel is rewritten in C.
- 1978: The C Programming Language is published.
- 1983: ANSI X3J11 begins standardization work.
- 1989: The ANSI C standard is published.
- 1990: C becomes an ISO standard.
- 1999: C99 adds new types and numerical features.
- 2011: C11 introduces, among other things, a concurrency memory
model.
- Today: C remains central to systems, embedded software, and
software interfaces.
Frequently asked questions
Did Dennis Ritchie create C alone?
He was its principal designer, but C emerged from Bell Labs’ collective
environment through B, Unix, users, and compiler work.
Is C portable by nature?
It enables portability, but programs may depend on hardware details or
extensions. Real portability requires following the standard and
isolating platform-specific code.
How do C and C++ differ?
C++ derives from C and adds classes, templates, overloading, and
deterministic resource management. They now have separate standards and
incomplete compatibility.
Why does C have no garbage collector?
Its design favors a small runtime and explicit resource control.
Implementations can add collectors, but the standard does not require
one.
Is C still a good first language?
It teaches data representation and memory behavior but quickly exposes
complex risks. Its suitability depends on the learning objective and
domain.
Will safer languages replace C?
Some new projects choose Rust or other languages when memory safety
dominates. The scale of existing C code, tools, and interfaces
guarantees a long coexistence.