How Did B Language Syntax Differ From C?
The transition from Ken Thompson's B programming language to Dennis
Ritchie's C marked the foundation of modern systems programming. While C
directly inherited B's expression-driven design, brace-delimited blocks,
and control-flow keywords like if, while, and
return, it diverged sharply in syntax to accommodate
hardware evolution. Most notably, C introduced explicit data types,
complex pointer declaration syntax, and preprocessor directives,
completely replacing B's typeless single-word memory model, archaic
assignment operators, and runtime vectors.
Typeless Declarations vs. Explicit Type Systems
The most fundamental syntactic distinction lies in how variables and functions are declared. B was a typeless language designed around a single architectural machine word. Every variable, parameter, or array cell held one word of data, leaving the interpretation of values entirely to the operators acting upon them.
Declarations in B used the auto keyword for local stack
variables and extrn for external variables:
auto x, y, z;
extrn buffer;
Because B lacked primitive types like char,
float, or double, variable definitions
contained no type specifiers. When Dennis Ritchie developed C, the
primary goal was to support byte addressing on the PDP-11 architecture.
Consequently, C replaced B's typeless declaration style with mandatory
type keywords:
int x, y;
char z;
extern char *buffer;Array Allocation and Pointer Mechanics
In B, array declarations had explicit allocation semantics tied
directly to memory pointers. Defining an array v[10]
created a dedicated named pointer variable v initialized to
point to a contiguous block of 10 words. Accessing an element relied on
the vector evaluation rule where v[i] translated
syntactically and semantically to *(v + i).
auto v[10];
v[2] = 5;
In contrast, C eliminated the dedicated pointer variable allocated
for arrays. In C, declaring int v[10] allocates only the
array storage itself. The identifier v automatically decays
to a pointer to its first element in expressions, removing the overhead
of maintaining an independent pointer word in memory. Furthermore, C
formalized pointer dereferencing with dedicated unary operators
(* and &) during declaration and usage,
whereas B relied heavily on unary * (indirection) without
explicit pointer types.
Compound Assignment Operators
B inherited assignment operators directly from BCPL, which led to syntactic ambiguity. In B, compound assignments placed the assignment operator before the arithmetic operator:
=+meant add and assign=-meant subtract and assign=\*meant multiply and assign
This order frequently created lexer ambiguities. For example, the
expression x =- 1 could be parsed either as subtracting
1 from x (x = x - 1) or as
assigning negative one to x (x = -1). To
resolve this parser confusion, C flipped the syntax order to the modern
format used today:
x += 1;
x -= 1;
x *= 2;Function Definitions and Return Syntax
Function headers in B did not specify return types or parameter
types. Parameters were listed in parentheses, and local declarations
occurred inside the function body using auto:
sum(a, b) {
auto total;
total = a + b;
return(total);
}
In early K&R C, functions gained return types, and parameter declarations were placed between the parameter list and the opening brace:
int sum(a, b)
int a, b;
{
int total = a + b;
return total;
}Modern C further standardized this into ANSI/ISO function prototypes
where types accompany parameters directly inside the parentheses.
Additionally, B typically enclosed return values in parentheses as
return(expr);, whereas C formalized return as
a keyword where parentheses around the returned expression are
optional.
Strings, Characters, and Macro Handling
B handled character data by packing multiple characters into a single machine word. Single quotes in B defined string-like constants that packed characters directly:
x = 'hello';
Because B lacked an integrated preprocessor, constant replacement,
file inclusions, and macro evaluations had to be handled externally or
via language-specific built-ins. C separated single characters from
character strings by enforcing single quotes for individual character
literals ('a') and double quotes for null-terminated byte
strings ("hello"). C also introduced the
#include and #define preprocessor syntax,
formalizing macro expansion and header inclusion directly into the
compilation pipeline.