How C Became a Standard Language Instead of One Compiler's Dialect
Follow C from Bell Labs and K&R practice through ANSI X3J11, ISO C and C23, including what portability guarantees—and deliberately leaves open.
C did not begin as a language designed by committee, nor was it merely one vendor compiler frozen into law. It evolved at Bell Labs while Unix and its hardware targets changed, spread through source, documentation and portable compilers, accumulated incompatible local interpretations, and then became the subject of a six-year ANSI negotiation. The result standardized a language family while deliberately preserving room for very different machines.
The standard’s achievement is often overstated. It did not guarantee identical integer sizes, object representation, byte order, ABIs, system calls, or results for undefined behavior. It created a common source-language and library contract precise enough for independent implementers and portable programs to meet.
C evolved with early Unix
Dennis Ritchie’s account traces C through Martin Richards’s BCPL and Ken Thompson’s B. B was compact and typeless, suited to the resource-constrained PDP-7 environment in which early Unix developed. Moving Unix to the PDP-11 exposed pressure for data types and generated code that fit the new machine.
Ritchie’s intermediate changes—sometimes described as “New B”—introduced typed objects and a compiler for the PDP-11. The language acquired the name C as arrays, pointers, structures, types and operators converged into a recognizable form. Structures were a particularly important step toward rewriting system code that described processes, files and devices.
By 1973 much of the Unix kernel had been rewritten in C. This did not make Unix hardware-independent in an absolute sense; assembly remained necessary and ports still required machine work. It demonstrated that an operating system could place far more implementation in a language portable enough to retarget.
Portable compilers spread a moving language
Bell Labs’ Portable C Compiler helped carry C and Unix to new architectures. Source written for a PDP-11 encountered machines with different word sizes, alignments, character behavior and calling conventions. Each port tested which language assumptions were essential and which were accidents of one implementation.
Distribution also created divergence. Compiler authors added features, fixed ambiguities differently, and preserved local source compatibility. Library names and behavior varied across Unix systems. Programs could be “C” in recognizable syntax yet depend on one compiler’s preprocessing, declaration, arithmetic or calling behavior.
This was not simply vendor sabotage. C’s early description was partly embodied in compiler behavior, Unix practice and evolving documents. Without a normative specification, independent implementers had no final authority for corner cases.
K&R C was a common reference, not a formal standard
Brian Kernighan and Dennis Ritchie’s The C Programming Language appeared in 1978. Its compact description and examples became the practical reference for programmers and compiler writers. “K&R C” now names the pre-standard dialect associated with that first edition.
The book dramatically improved common understanding, but a book written by the language’s creators is not the same institutional instrument as a consensus standard with conformance definitions, defect handling and national/international adoption. K&R reflected an implementation tradition and left questions that ports and extensions exposed.
Old-style function definitions illustrate the boundary. A caller could lack a checked prototype describing parameter types, and default promotions filled gaps. Implementations might agree for ordinary Unix code while disagreeing or fail silently at less common boundaries.
ANSI formed X3J11 to standardize existing practice
ANSI chartered committee X3J11 in 1983. Its task was not to invent a successor unrelated to deployed C. The committee had to codify widespread practice, resolve contradictions, support modern machines, specify a useful library, and preserve as much existing source as practical.
Those goals conflict. Freezing every historical quirk would preserve bad or machine-specific behavior. Redesigning everything would strand compilers and code. The committee’s published rationale describes a principle often summarized as codifying existing practice while providing a clear, portable language.
Participants came from compiler vendors, hardware companies, government, academia and large users. Drafts circulated and changed through comments and ballots. “ANSI C” was therefore a negotiated specification, not Dennis Ritchie handing one compiler’s source to ANSI.
C89 standardized both language and library
ANSI approved X3.159-1989 near the end of 1989. The specification formalized syntax, constraints, translation, types, expressions, declarations, preprocessing and library behavior. It standardized features such as function prototypes, void, qualifiers including const and volatile, and a defined set of headers/functions in a coherent contract.
Prototypes were more than syntax polish: they let compilers check calls and specify parameter conversions. volatile gave implementations a language-level signal for objects whose values can change outside ordinary abstract-machine evaluation, important for hardware and signal interactions, though not a universal concurrency primitive.
The library contract made functions such as formatted I/O, allocation, string handling, character classification and mathematics portable at the interface level. It did not require every host OS to implement Unix files, sockets, processes or terminals; those belong to other standards and platforms.
C90 internationalized substantially the same base
ISO/IEC adopted the language as ISO/IEC 9899:1990 with editorial/internationalization work. “C89” and “C90” are commonly used for the ANSI and ISO editions of substantially the same language generation, not two competing dialects separated by years of feature design.
The international WG14 process then maintained defect reports, technical corrigenda and amendments. A 1995 amendment added internationalization-related facilities before the next full revision.
Today “ANSI C” is ambiguous. It may mean the original 1989 standard, any ISO-conforming C implementation, or merely code avoiding compiler extensions. Precise technical writing should name the edition and mode, such as C90, C99, C11, C17 or C23.
Portability includes explicit categories of variation
The standard defines an abstract machine and observable behavior, then classifies boundaries. Implementation-defined behavior requires the implementation to choose and document an option, such as aspects of integer representation in historical editions or whether plain char behaves as signed or unsigned. Unspecified behavior permits one of several possibilities without requiring documentation for each choice. Undefined behavior imposes no requirements after a program violates the relevant rule.
These categories are not proof that the standard “forgot” portability. They let C target machines with different efficient representations and let optimizers reason from the program’s obligations. Portable code must remain within defined behavior and query implementation limits through standard facilities rather than assume a familiar desktop ABI.
The standard specifies minimum ranges and relationships for fundamental types, not one universal int width or endian order. It does not define structure padding identically, prescribe an executable format, or make separately compiled objects binary-compatible across compilers.
Hosted and freestanding C serve different environments
A hosted implementation supplies the full standard environment expected for ordinary applications, including program startup and the required library. A freestanding implementation targets kernels, embedded systems and other environments where only a smaller required library subset and different startup assumptions make sense.
This distinction helped standard C remain relevant to operating systems without pretending a kernel runs under a conventional host. A freestanding implementation is not automatically nonconforming because it lacks files or main in an ordinary hosted form; its conformance category is different.
It also means a successful C program can still be operating-system-specific. POSIX APIs, Windows APIs, compiler intrinsics and hardware registers can be valuable while sitting outside ISO C. Portability claims must identify the additional contract.
Revisions evolved rather than merely correcting typos
C99 added major facilities including // comments, declarations mixed with statements, inline, variable-length arrays, complex arithmetic, long long, designated initializers and a much larger library. C11 added a memory model, atomic and threading facilities, static assertions and other changes. C17 was primarily a defect-correction edition.
The revision developed under the name C23 was published as ISO/IEC 9899:2024, edition 5, in October 2024. The colloquial feature label and publication year therefore differ. WG14 has already moved to post-C23 work; a “latest C standard” claim must be dated.
Compatibility is a goal, not a promise that every old source remains valid forever. C99 removed implicit int and old implicit function declarations from conforming modern C. C11 removed the unsafe gets function. Implementations often offer legacy modes, but those modes are not evidence the current standard still requires the old feature.
Standards permit extensions without redefining C
Compilers can provide GNU, Microsoft, embedded or architecture-specific extensions, predefined macros and pragmas. A conforming implementation must still accept and correctly translate the required class of conforming programs; extensions cannot silently change the meaning of a strictly conforming program.
Build modes therefore matter. A compiler’s default “gnu” dialect may accept code rejected by a strict ISO mode. Testing with explicit edition and diagnostics distinguishes portable C from a useful extended dialect.
Standardization succeeded because it made that distinction discussable. Before X3J11, disagreement could reduce to “my compiler accepts it.” Afterward, programmers could ask whether code satisfied a published contract, relied on documented implementation behavior, or used an extension.
C became durable not by eliminating machine differences but by specifying which differences programs may observe and what implementations must document. That is a narrower achievement than “write once, run identically everywhere,” and a much more technically significant one than blessing a single compiler. Related: How to Trace a Technology’s Lineage Through Patents and Standards Documents · No, Bill Gates Never Said ‘640K Ought to Be Enough for Anyone’