| B Language Reference |
B is a typeless systems programming language created by Ken Thompson and Dennis Ritchie at Bell Labs around 1969. It is the direct predecessor of C. In B, all data is a 16-bit machine word — there are no type declarations. A value can be an integer, a character, a memory address, or a boolean, all in the same variable.
B was designed for systems programming on the PDP-11, a 16-bit minicomputer. Its core principles are:
All values in B are 16-bit words (2 bytes). There is no concept of char or byte as a first-class type — characters are simply small integers (0-255) stored in a word.
| Concept | B Representation | Example |
|---|---|---|
| Integer | 16-bit signed word (-32768 to 32767) | 42, -1, 0xFFFF |
| Character | 8-bit value in low byte of a word | 'A' = 65, '\n' = 10 |
| Memory address | 16-bit unsigned word | 0x0100, buf |
| Boolean | 0 = false, non-zero = true | Conditional expressions |
| Array | Consecutive words in memory | arr[10] = 20 bytes |
| String | Array of character values, null-terminated | "hello" |
Word addressing: Array indices are word-based. arr[1] accesses the second word, which is 2 bytes after arr[0].
Declared at the top level (outside any function). Placed in the DATA or BSS section. Initialized to zero. Visible to all functions in the same file.
counter; /* single word, initialized to 0 */ buffer[256]; /* array of 256 words (512 bytes) */ flag, index; /* multiple globals */
Declared with auto inside a function. Allocated on the stack frame when the function is called. Not preserved between calls (no recursion-safe locals in the original B, but this compiler does support reentrant functions via stack allocation). Initial value is undefined — always initialize before use.
auto i, j, k; /* three local words */ auto buf[10]; /* local array of 10 words (20 bytes) */ auto x = 42; /* initialization (compiler extension) */
Declared with extrn. Used for functions or variables defined in other modules or libraries. Must appear before the first use.
extrn printf; /* external function */ extrn putchar; /* external function */ extrn global_var; /* external variable */
Function parameters are declared in the function definition header. They behave like local variables initialized by the caller. The compiler accesses them via the frame pointer at positive offsets (ix+N on Z80, bp+N on 8086).
add(a, b) {
return a + b; /* a and b are parameters */
}
| Prec | Assoc | Operators | Description |
|---|---|---|---|
| 1 | L→R | () [] . | Function call, array index, member access |
| 2 | R→L | ! ~ - ++ -- | Logical NOT, bitwise NOT, unary minus, increment, decrement |
| 3 | L→R | * / % | Multiply, divide, modulo |
| 4 | L→R | + - | Add, subtract |
| 5 | L→R | << >> | Left shift, right shift |
| 6 | L→R | < > <= >= | Relational comparison |
| 7 | L→R | == != | Equality, inequality |
| 8 | L→R | & | Bitwise AND |
| 9 | L→R | ^ | Bitwise XOR |
| 10 | L→R | | | Bitwise OR |
| 11 | L→R | && | Logical AND (short-circuit) |
| 12 | L→R | || | Logical OR (short-circuit) |
| 13 | R→L | = += -= *= /= %= &= |= ^= | Assignment and compound assignment |
auto a, b, c; a = 10; b = 3; c = a + b; /* 13 */ c = a - b; /* 7 */ c = a * b; /* 30 */ c = a / b; /* 3 (integer division, truncates toward 0) */ c = a % b; /* 1 (remainder) */ c = -a; /* -10 (unary minus) */ c = a + b * 2; /* 16 (operator precedence) */
auto a, b; a = 0xFF; /* 11111111 */ b = 0x0F; /* 00001111 */ a & b; /* 0x0F = 00001111 (AND) */ a | b; /* 0xFF = 11111111 (OR) */ a ^ b; /* 0xF0 = 11110000 (XOR) */ ~a; /* 0xFF00 (NOT, 16-bit) */ a << 2; /* 0x03FC (shift left) */ b >> 1; /* 0x0007 (shift right) */
auto x, y; x = 10; y = 3; x < y; /* 0 (false) */ x > y; /* 1 (true) */ x <= 10; /* 1 (true) */ y >= 3; /* 1 (true) */ x == 10; /* 1 (true) */ x != y; /* 1 (true) */
Comparisons return 1 for true and 0 for false. These are ordinary integer values that can be used in arithmetic.
auto a, b; a = 10; b = 0; a && b; /* 0: a is true, b is false, result false */ a || b; /* 1: a is true, short-circuits, result true */ !a; /* 0: logical NOT of non-zero is 0 */ !b; /* 1: logical NOT of zero is 1 */
Short-circuit: In a && b, if a is 0, b is never evaluated. In a || b, if a is non-zero, b is never evaluated.
auto i, j; i = 5; j = ++i; /* i=6, j=6 (pre-increment) */ j = i++; /* j=6, i=7 (post-increment) */ j = --i; /* i=6, j=6 (pre-decrement) */ j = i--; /* j=6, i=5 (post-decrement) */
Note: Prefix and postfix operators return values (rvalues), not lvalues. The increment/decrement applies to the storage location.
if (x > 0)
putchar('+');
else if (x < 0)
putchar('-');
else
putchar('0');
/* Multi-statement blocks require braces */
if (x) {
putchar('T');
x = 0;
} else {
putchar('F');
x = 1;
}
auto i;
i = 0;
while (i < 10) {
putchar('0' + i);
i = i + 1;
}
auto i;
for (i = 0; i < 10; i = i + 1)
putchar('0' + i);
/* Multiple expressions in for */
auto i, j;
for (i = 0, j = 9; i < j; i = i + 1, j = j - 1)
putchar('.');
auto n;
n = 0;
do {
putchar('0' + n);
n = n + 1;
} while (n < 5);
break exits the innermost while, for, or do loop immediately:
auto i;
i = 0;
while (1) {
if (i >= 10) break;
putchar('0' + i);
i = i + 1;
}
return expr; exits the current function and provides the return value to the caller. The return value is placed in HL (Z80/8080/8085) or AX (8086).
abs(n) {
if (n < 0) return -n;
return n;
}
name(param1, param2, ...) {
declarations
statements
}
result = add(10, 20);
putchar('A');
printf("value = %d", 42); /* printf from runtime library */
auto r;
r = add(mul(2, 3), div(10, 2)); /* r = 6 + 5 = 11 */
printf("deep = %d", foo(bar(baz(1, 2), 3), 4));
fib(n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
B functions are fully reentrant and support recursion. Each call allocates a new stack frame.
B allows calling a function with fewer arguments than declared. Extra parameters will contain garbage values. This is used by printf:
printf(fmt, a, b, c, d, e, f, g, h) { ... }
printf("Hello"); /* only 1 arg used */
printf("%d %d", 10, 20); /* 3 args used */
Stack layout for printf("x=%d y=%d", 42, 99):
Higher addresses:
[param 8: h] (garbage — not passed)
[param 7: g] (garbage)
[param 6: f] (garbage)
[param 5: e] (garbage)
[param 4: d] (garbage)
[param 3: c] (garbage)
[param 2: b] 99 ← pushed 3rd
[param 1: a] 42 ← pushed 2nd
[param 0: fmt] ptr ← pushed 1st
[return addr]
[saved frame ptr] ← frame pointer (ix/bp/bc)
Lower addresses (SP):
buf[256]; /* 256 words (512 bytes) in BSS */
arr[10]; /* 10 words */
main() {
buf[0] = 65; /* 'A' */
buf[1] = 66; /* 'B' */
putchar(buf[0]); /* prints 'A' */
}
main() {
auto arr[10]; /* 10 words on stack frame */
auto i;
i = 0;
while (i < 10) {
arr[i] = i * 2;
i = i + 1;
}
}
Arrays are word-indexed. arr[i] accesses the word at address arr + i * 2 (since each word is 2 bytes). The compiler multiplies the index by 2 automatically.
arr[0] /* first word, at address arr + 0 */ arr[1] /* second word, at address arr + 2 */ arr[2] /* third word, at address arr + 4 */
B has a string literal type. String literals are stored in the DATA section as a sequence of bytes (one per character) followed by a null word (dw 0). Strings are accessed via peekb() for byte-by-byte reading.
extrn printf;
main() {
printf("Hello world!"); /* string passed by address */
}
/* String storage (generated by compiler):
.L1: db "Hello world!"
dw 0
*/
Manual string processing with peekb:
print_str(s) {
auto i, ch;
i = 0;
ch = peekb(s + i); /* read byte at s[i] */
while (ch != 0) {
putchar(ch);
i = i + 1;
ch = peekb(s + i);
}
}
The B compiler includes a C-compatible preprocessor that runs before lexing. Directives start with # at the beginning of a line.
#define BUFSIZE 256 #define MAX(a, b) ((a) > (b) ? (a) : (b)) buf[BUFSIZE]; x = MAX(10, 20);
#define DEBUG 1
#ifdef DEBUG
printf("Debug mode\n");
#endif
#ifndef RELEASE
printf("Development build\n");
#endif
#include "myheader.h" /* local file */ #include <stdlib.b> /* include path */
The compiler recognizes four special function names and generates inline code instead of a function call. These provide direct memory access without the overhead of a call/return.
| Function | Operation | Z80 Code | 8086 Code |
|---|---|---|---|
| peekb(addr) | Read byte from memory | ld a, [hl] ; or equivalent |
mov bx, ax xor ah, ah mov al, [bx] |
| pokeb(addr, val) | Write byte to memory | ld [hl], e ; or equivalent |
mov [bx], al |
| peekw(addr) | Read 16-bit word from memory | Implemented as *(addr) dereference | |
| pokew(addr, val) | Write 16-bit word to memory | Implemented as *(addr) = val store | |
auto ch, addr; addr = 0x8000; pokeb(addr, 0x41); /* write 'A' to memory location 0x8000 */ ch = peekb(addr); /* read back → ch = 0x41 */
The asm() statement allows embedding raw assembly instructions directly in B code:
asm("ld a, 0"); /* Z80 */
asm("mov ax, 0"); /* 8086 */
asm("hlt"); /* halt CPU */
| Aspect | Convention |
|---|---|
| Parameter passing | Stack, right-to-left. Rightmost argument pushed first, at lowest address. |
| Evaluation order | Right-to-left (two-pass technique, no reversal step needed) |
| Stack cleanup | Caller cleans. After call: add sp, N*2 where N is the argument count. |
| Return value | HL register pair (Z80/8080/8085), AX register (8086) |
| Frame pointer | IX (Z80), BP (8086), BC (8080/8085) |
| Scratch registers | All registers except frame pointer and stack pointer may be modified by callee |
For a detailed explanation of stack frames and register usage, see the Calling Convention page.
| Feature | B | C |
|---|---|---|
| Type system | Typeless (one word type) | Static types (int, char, long, struct) |
| Variable declaration | auto x; (inside functions) | int x; (anywhere) |
| Storage classes | auto, extrn | auto, static, extern, register |
| Structures | Not supported | struct { ... } |
| Pointers | Implicit (word = address) | Explicit (int *p) |
| Array/pointer equivalence | Limited | Full decay semantics |
| Ternary operator | ?: not available | a ? b : c |
| switch/case | Not available | Full support |
| Function prototypes | Not needed | Recommended |
| Standard library | Minimal (printf, putchar, string fns) | Full stdlib |
extrn printf;
main() {
printf("Hello, World!");
}
extrn putchar;
main() {
auto i;
i = 65; /* 'A' */
while (i <= 90) { /* 'A' through 'Z' */
putchar(i);
i = i + 1;
}
putchar('\n');
}
fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
}
strlen(s) {
auto i;
i = 0;
while (peekb(s + i) != 0)
i = i + 1;
return i;
}
extrn putchar;
extrn printf;
calc(a, b, op) {
if (op == '+') return a + b;
if (op == '-') return a - b;
if (op == '*') return a * b;
if (op == '/') return a / b;
return 0;
}
main() {
auto r;
r = calc(10, 3, '+');
printf("10 + 3 = %d\n", r);
r = calc(10, 3, '*');
printf("10 * 3 = %d\n", r);
}
extrn printf;
arr[20];
main() {
auto i, sum;
i = 0;
while (i < 20) {
arr[i] = i * 3;
i = i + 1;
}
sum = 0;
i = 0;
while (i < 20) {
sum = sum + arr[i];
i = i + 1;
}
printf("Sum of 0..19 * 3 = %d\n", sum);
}
extrn printf;
#define FLAG_READ 1
#define FLAG_WRITE 2
#define FLAG_EXEC 4
main() {
auto flags;
flags = FLAG_READ | FLAG_WRITE; /* 3 */
if (flags & FLAG_READ) printf("Readable\n");
if (flags & FLAG_WRITE) printf("Writable\n");
if (flags & FLAG_EXEC) printf("Executable\n");
flags = flags | FLAG_EXEC; /* add exec */
printf("New flags = %d\n", flags); /* 7 */
}
| [B Compiler] · [Calling Convention] · [Samples] | © 2025-2026 HC SDK |