Programming Languages

The C Language and Portable System Software

How a compact language developed at Bell Labs in the early 1970s helped Unix and other operating systems move between very different machines.

On this page
  1. Operating systems in assembly
  2. Unix and a small language
  3. Why a small language mattered
  4. Portability in practice
  5. Spreading beyond Unix
  6. The costs of closeness to the machine
  7. Why this history matters

Operating systems in assembly

Through the 1960s, the programs that control a computer’s hardware were generally written in assembly language. This made them fast and gave their authors close control, but it also tied each system to a particular processor. Moving an operating system to a different kind of machine meant rewriting it almost from scratch.

There were exceptions. Some large projects of the 1960s wrote system software in higher-level languages, and these experiments demonstrated that it was possible. But the languages involved were often considered too large or too slow for general use, and many practitioners remained convinced that serious system code required assembly.

Unix and a small language

At Bell Labs in the late 1960s, a group including Ken Thompson and Dennis Ritchie worked on a small operating system that became Unix. It was first written in assembly language for a modest minicomputer. Thompson created a very small language, B, derived from earlier languages, and Ritchie then developed an improved language during the early 1970s, which he called C, the next letter after B.

One reason for the change was that B treated all data in much the same way, which fit poorly on machines that distinguish between characters, whole numbers and other kinds of values. C added data types and other features while staying small. Around 1973, much of the Unix kernel was rewritten in C. The date is given in general terms here because accounts of this period vary in detail, but the decade is well established.

Why a small language mattered

Several design choices helped C do its job:

  • Small core. The language itself was compact, and much of the useful functionality, such as input and output, came from a library rather than the language definition. That made it easier to build a compiler for a new machine.
  • Close to the hardware. C let programmers work directly with memory addresses through pointers, which is useful for tasks such as managing memory or controlling devices.
  • Efficient output. A C compiler could translate code into machine instructions in a fairly direct way, so programmers could write system software without feeling they were paying a large penalty.
  • Structured programming. It offered loops, functions and blocks that made code clearer than assembly.

Together these traits made it possible to write most of an operating system in a high-level language while leaving a small amount of assembly for the parts that truly depended on the processor.

Portability in practice

The result was that Unix could be moved to different hardware by writing a new C compiler, adjusting the machine-dependent parts and recompiling the rest. By the late 1970s, researchers had shown the system running on more than one type of computer. This was unusual at a time when software was generally tied to a vendor’s machines.

Portability was not perfect. Programs still contained assumptions about word sizes, byte order and other hardware features, and moving them required careful work. But the principle was demonstrated: the same source text could serve multiple architectures. That idea encouraged universities, which could obtain Unix under license at modest cost, to run it on a variety of machines, and it helped spread both Unix and C in education.

In 1978, Brian Kernighan and Dennis Ritchie published a book describing the language, which for years served as a de facto definition. A formal standard was produced by an American national standards body in the late 1980s, followed by international standardization.

Spreading beyond Unix

C became widely used for other purposes. Compilers, text editors, databases and utilities were written in it. As personal computers and microprocessors spread in the late 1970s and 1980s, C compilers were available for them too, and the language was used for much of the software that ran on those machines. Many desktop applications, and later parts of mobile systems, were built with C or its relatives.

It also became a base for other languages. C++ extended it with classes, Objective-C added a message-passing style, and Java and many scripting languages borrowed its syntax for expressions, braces and loops. A programmer familiar with C often finds other languages readable, which is part of its cultural influence. Interpreters for scripting languages are often themselves written in C.

The costs of closeness to the machine

C’s flexibility came with risks. Because the language trusts the programmer, it does not automatically check that a program stays within the memory it was given. Mistakes such as writing beyond the end of an array can cause crashes or unexpected behavior, and these kinds of errors have been a source of security vulnerabilities in many programs over the years. The history of computer security includes many examples of this problem, though it would be inaccurate to blame the language alone; careful practice, testing and tooling also matter.

Newer languages have been designed to reduce such risks while still being suitable for low-level work. Whether they will replace C in areas such as operating systems and embedded devices is still debated, and large existing code bases suggest that C will remain in use for a long time.

Why this history matters

C illustrates a broader point about systems programming: choosing the right level of abstraction can change what is practical. By offering enough control to write an operating system and enough abstraction to be moved between machines, C helped separate software from specific hardware in a way that earlier system code rarely achieved. That separation is part of why software today is developed independently from the chips that run it.

It also shows that influential technology is often modest in origin. C was designed by a small team for their own use, not as a grand project, and its success came largely from solving a concrete problem well enough that others adopted it.

The complete guide to programming languages

A Plain-Language History of Programming Languages
Read the guide

Keep exploring

Guides from other topics

All topics →