B on the PDP-11: Why a Word-Oriented Language Gave Way to C
Examine B's single-cell values, word-indexed arrays, packed strings, and threaded interpreter, and how PDP-11 byte-addressing pushed Bell Labs toward C.
B is often compressed into the phrase “C without types.” That is a useful mnemonic, but it hides the engineering problem that made B historically important. B treated memory as an array of word-sized cells and let context determine how a cell should be interpreted. That model fit the early machines on which Bell Labs used BCPL and B. When the work moved to the byte-addressed PDP-11, character access and pointer arithmetic exposed a mismatch between the language’s word-oriented model and the processor’s byte addresses.
The story is best read through two primary records: Ken Thompson’s Users’ Reference to B, an internal Bell Labs memorandum dated January 7, 1972, and Dennis Ritchie’s later first-person account of C’s development. Thompson’s manual documents the PDP-11 version programmers could use. Ritchie explains why its assumptions became awkward on that machine. The distinction matters: the manual records a specific implementation; the retrospective interprets a language transition over several years.
A compact system language with one cell-sized value
Thompson described B as a language for recursive, primarily non-numeric work such as system programming. Its syntax was compact, its compiler relatively small, and its runtime library supplied operating-system services. The B manual says a PDP-11 rvalue was a 16-bit pattern. It also names conceptual object kinds such as integers, labels, vectors, and functions, but does not provide static type checking that verifies every use against those kinds. An expression’s interpretation depended on the context in which the value was consumed.
This was not the same as saying that every bit pattern was interchangeable without consequences. A function address used as an integer, or a vector address used in an operation that expected another representation, could produce a bad result. The important difference from later typed C was that the compiler did not enforce a declared type for each object and use it to check those relationships. The programmer and runtime conventions carried more of that burden.
The design descended from BCPL, whose “word” or “cell” was likewise a fixed-length bit pattern. B simplified and reshaped that lineage for a constrained Bell Labs environment. Ritchie recalled that Thompson created it for the PDP-7 after a brief attempt to use Fortran. The PDP-7 had only 8K 18-bit words in the early setup Ritchie described, so a small compiler and runtime were practical requirements, not aesthetic extras.
Arrays and pointers counted cells
In B, a vector name held the memory index of its first cell. If V points to a vector, V[i] is shorthand for *(V+i): add the index i to the base cell index, then access that cell. Advancing a pointer by one therefore means advancing by one B cell, not by one byte.
The PDP-11 made that distinction operational. The B manual describes its rvalues as 16 bits, while the machine’s memory instructions and addresses could refer to bytes. On a 16-bit word, converting a cell index to a byte displacement requires scaling by two. Ritchie says B’s word-indexed pointers needed a runtime scale conversion when used with the PDP-11’s byte addresses. This did not make B’s array rule illogical: it reflected the language’s cell-based memory model. It did mean that code wanting byte-oriented data had to cross an abstraction boundary repeatedly.
| Concern | B’s PDP-11 model | Pressure exposed by the hardware |
|---|---|---|
| Basic value | One 16-bit cell whose interpretation depends on use | Distinguishing character and integer data requires extra conventions |
| Vector index | V[i] selects the i-th word-sized cell |
Byte-oriented access needs packing, extraction, or address scaling |
| Pointer step | p + 1 advances one cell |
The corresponding machine address step depends on the cell width |
| Character string | Characters are packed into cells and terminated by *e |
A machine byte is not a separately typed B object |
The table describes the B implementation and the mismatch Ritchie identified; it is not a claim that every BCPL or B compiler used identical memory layouts. Ritchie’s history explicitly distinguishes language conventions from implementation details across machines.
Character strings needed library help
B strings were vectors of packed characters terminated by a special end marker written *e. The language did not introduce a char object type in the PDP-11 manual’s B model. Instead, its library provided char(string, i) to retrieve the i-th character and lchar(string, i, value) to replace it. These routines made character operations possible, but they placed an extra procedure boundary between ordinary indexing and the machine’s byte-oriented storage.
The manual’s formatting routine makes this concrete. It advances through a format string by calling char, then dispatches on the conversion letter. A separate routine reads a vector of argument values. The program can do useful systems work, but character interpretation is an explicit convention layered on top of the cell model rather than a built-in typed pointer operation.
That distinction explains one part of the move toward C without turning the history into a simple story about “safety.” Ritchie identified character handling, word-to-byte pointer conversion, and anticipated floating-point support as practical pressures. He specifically wrote that type safety and interface checking did not seem as important to the developers at that point as they later would. Retrospective accounts should preserve that chronology instead of projecting modern motivations backward.
An authentic B example: recursive radix output
Thompson’s manual includes a short printn routine. It prints a non-negative integer in a base from 2 through 10 by recursively printing the quotient before the remainder. The example uses an auto local, an extrn library reference, assignment inside a condition, and a character output routine, all without declaring n, b, or a as int objects.
printn(n, b) {
extrn putchar;
auto a;
if (a = n / b)
printn(a, b);
putchar(n % b + '0');
}
For n = 42 and b = 8, the quotient chain is 42, 5, 0. The recursive call emits 5 first; the initial call then emits the remainder 2, producing 52. The routine’s documented input limits matter: this digit conversion works for non-negative values and bases 2 through 10, where adding a remainder to '0' selects a decimal digit character. This snippet is a transcription of the 1972 B example, not C code and not a claim that a current host compiler accepts B syntax. Its observable algorithm can be checked independently: repeated quotient/remainder decomposition of 42 in base 8 yields digits 5 then 2.
The example also reveals why B’s syntax was attractive for a small systems tool. Recursion, local automatic storage, vectors, and library I/O are present in a compact language. Yet the same routine’s arithmetic and character operations share the same underlying cell representation. A compact expression is not evidence that the language already had the later type model.
The interpreter made portability possible, but performance mattered
Ritchie’s account says the PDP-7 B compiler emitted threaded code: a sequence of addresses of small interpreter routines, rather than native machine instructions for each program operation. The PDP-11 B manual documents a related reverse-Polish threaded interpreter and describes the interpreter’s program counter, display pointer, stack, and call frames. The manual even compares one small sequence against a direct three-instruction equivalent and estimates roughly a five-to-one speed penalty and a two-to-one space penalty for that comparison. Those figures belong to that example and implementation; they are not a universal benchmark for every B program.
Threaded code helped the small language run across more than one machine, but it did not make B suitable for every system component. Ritchie says the PDP-7 implementation was too slow and constrained for a wholesale rewrite of the operating system. B still supported experiments and useful tools. When the PDP-11 arrived, the team ran B by supplying operator code fragments and wrote a simple assembler in B; Ritchie recalls that dc was an early program tested on the machine before an operating system was running.
By 1971, B was being used for more new programs at Bell Labs, including Steve Johnson’s early yacc. Its utility and limitations coexisted. The language was useful enough to spread, while its interpreter overhead and word-oriented assumptions created concrete reasons to change the compiler and data model.
NB was an engineering transition, not an overnight replacement
Ritchie dates the first extensions toward C to 1971. He added char and rewrote the compiler to emit PDP-11 instructions instead of threaded code; he called the intermediate language NB, or “new B.” NB introduced int and char, arrays of them, and pointers to them. The goal was not to discard B’s successful syntax and programming idioms all at once. It was to make the representations fit the machine more directly.
Early NB still treated arrays in a way closely related to B. Ritchie then encountered a problem when trying to add structures: storing a hidden pointer inside an array-containing structure would make the structure fail to describe the exact bytes already present in memory. His later C rule instead creates a pointer to the first array element when the array name appears in an expression. Typed pointer arithmetic can then scale an index by the size of the pointed-to object. That transition preserved much familiar indexing while removing the need to materialize an array-base pointer inside each object.
The history also corrects a persistent ++ myth. Ritchie says the operator appeared in B before a PDP-11 existed, so it was not invented to exploit the PDP-11’s auto-increment addressing mode. The PDP-7 had a small number of auto-increment memory cells that may have suggested the idea, but Ritchie labels that connection as probable rather than certain. He gives a more direct implementation motivation: ++x could translate to less code than x = x + 1.
How to inspect the period evidence
When comparing early language manuals, separate three layers: the language’s written rules, one implementation’s memory and compiler behavior, and the operating system services supplied by its library. For the PDP-11 B manual, record its date and target, then check the sections on rvalues, vectors, char/lchar, compiler phases, and threaded-code implementation. Do not treat a modern C compiler as a B compatibility test; the source language and execution model differ.
The manual also records a multi-stage toolchain: a B translation phase, conversion to assembler source, assembly, and linking with B runtime libraries. Its /etc/bc, /etc/ba, and ld paths describe that historical Unix installation, not commands to run on a modern workstation. For preservation, cite the source manual page and section, identify whether a statement comes from the period manual or Ritchie’s later account, and keep any reconstructed test or emulator separate from the original artifact.
B’s historical significance is not that it was an incomplete C. It made a word-sized, typeless model productive in a tiny system environment, and its limitations became visible when the hardware asked programs to reason directly about bytes. The path from B through NB to C preserved useful conventions while changing the relationship between values, memory, and addresses. That is a more precise account of how machine constraints shaped one of computing’s most influential language lineages.
Related:
- DEC PDP-11: A General Register Machine Built Around the UNIBUS
- DEC PDP-8: The 12-Bit Minicomputer That Put Computing Near the Work
Sources:
- Ken Thompson, Users’ Reference to B (January 7, 1972), PDP-11 language manual and implementation notes
- Dennis M. Ritchie, The Development of the C Language (1993), author-courtesy reprint with 1993 ACM copyright notice
- Martin Richards, BCPL Reference Manual (1967), archived with Dennis Ritchie’s notes on the BCPL-to-B lineage