Der Autor Sebastian beschäftigte sich mit der ersten Auflage von "The C Programming Language", dem C89-Rationale-Dokument und dem Quellcode des C-Compilers der 7. Unix-Edition, um den vor-ANSI-Dialekt K&R C zu dokumentieren. Er merkt an, dass die vielgescholtene kontextsensitive Grammatik der Sprache in ihrem ursprünglichen Umfeld weniger absurd wirkte: der erste C-Compiler war ein Single-Pass-Compiler auf speicherarmen Maschinen, sodass der Parser ohnehin Identifikatoren und Scopes verfolgte. Funktionsprototyp-Scopes gab es noch nicht, wodurch die modernen Scoping-Probleme, die die Grammatik heute so schwierig machen, erst gar nicht auftraten.

Das Typsystem sah ganz anders aus. void war eine ANSI-Erfindung; Funktionen lieferten stillschweigend int zurück, und char * diente als generischer Zeiger, weil Zeiger beliebig zueinander zugewiesen werden konnten. long double gab es nicht, aber long float war ein Synonym für double. Das Schlüsselwort signed existierte nicht; nur unsigned stand zur Verfügung, und es fungierte zusammen mit short und long als Adjektiv zu int. Nur ein Adjektiv war erlaubt, also war long int gültig, aber long oder unsigned short int nicht. Die Reihenfolge war frei, sodass auch int long gültig war. Auch die Integer-Promotion war ungeregelt: Compiler unterschieden sich zwischen "unsigned-preserving" und "value-preserving". Bei ersterem war -1 < (unsigned short)0 falsch, weil die Operanden zu unsigned int befördert wurden; bei letzterem war es wahr, weil unsigned short in signed int passt. Das C89-Komitee entschied sich für value-preserving, obwohl Unix-Compiler in die andere Richtung gingen.

Konstanten und Literale brachten eigene Überraschungen. Es gab kein U-Suffix und tatsächlich überhaupt keine unsigned-Konstanten. Eine nicht-dezimale Konstante, die in den Wertebereich von unsigned int passte, erhielt dennoch Typ int, sodass 0x8000 auf einem 16-Bit-int negativ wurde. Oktale Konstanten akzeptierten 8 und 9 als Ziffern, also bedeuteten 078 und 0100 denselben Wert; das galt auch für Oktal-Escapes. Einige Escapes fehlten: \a, \v und \x existierten nicht. Ungültige Escapes waren keine Fehler; der Backslash wurde einfach stillschweigend entfernt, wodurch \" und \? praktisch schon funktionierten, bevor sie standardisiert wurden. Fließkomma-Konstanten waren immer double; keine f- oder l-Suffixe existierten.

Weil int als Maschinenwort gedacht war, lieferten Operationen, die heute size_t oder ptrdiff_t ergeben, int. size_t und ptrdiff_t selbst sind ANSI-Erfindungen. Daraus folgte, dass kein Typ größer als INT_MAX sein konnte. const und volatile fehlten ebenfalls. C89-Entwürfe enthielten einen noalias-Qualifizierer, aber Dennis Ritchie hielt ihn für unbrauchbar, und er wurde vor der Veröffentlichung gestrichen. Die Erstauflage von K&R, 2. Auflage, erwähnte noalias noch, spätere Auflagen entfernten ihn. String-Literale waren als eigenständige veränderbare Objekte definiert, was erklärt, warum Literale heute noch Typ char * haben: const gab es nicht, also wäre die Einführung inkompatibel gewesen.

Einige reservierte, aber ungenutzte Schlüsselwörter tauchen auf. entry war reserviert, ohne je in der Grammatik aufzutauchen. Zeilenfortsetzungen mit abschließendem Backslash waren auf Zeichenkonstanten, String-Literale und Präprozessordirektiven beschränkt; der ANSI-Ausschuss erlaubte sie überall, weil das einfacher war. Funktionsprototypen mit typisierten Parameterlisten waren eine ANSI-Erfindung. In K&R C verwendeten Funktionsdeklaratoren leere Klammern, und nur Definitionen listeten Parameternamen. Das machte variadische Funktionen undeclarierbar; daher war die spätere Forderung nach einem expliziten printf-Prototypen ein stiller Breaking Change. Die Grammatik für Funktionsdefinitionen im K&R-Buch ist ebenfalls falsch: sie beschreibt einen normalen Deklarator gefolgt von einer Parameterliste und ignoriert die übliche Spiral-Präzedenz der Deklaratoren. Auch die #line-Grammatik ist fehlerhaft; sie verlangt einen Pflicht-Identifier, wo ein String-Literal hingehört, und widerspricht damit dem Prosa-Text, der ihn optional nennt.

Funktionen und Zeiger verhielten sich anders. Array-Parameter fielen zu Zeigern ab wie in modernem C, aber Funktionsparameter wurden nicht zu Zeigern konvertiert; sie waren schlicht verboten. Funktionszeiger konnten in Aufrufausdrücken nicht direkt verwendet werden; ein expliziter Dereferenzierungsoperator war nötig. Die implizite Umwandlung von float-Parametern zu double geschah nur in der K&R-Form, nicht in der Prototyp-Form, und blieb im Standard bis C23, das die K&R-Form ganz entfernte. Der linke Operand eines Shift-Ausdrucks war eine Ausnahme von den üblichen arithmetischen Promotionen: in Standard-C hat 1 << 1L Typ int, während K&R den linken Operanden zu long beförderte.

Structs und Unions waren weit eingeschränkter. Man konnte keinem Struct- oder Union-Lvalue zuweisen, Funktionen konnten sie weder annehmen noch zurückgeben, und ihre Initialisierer mussten geschweifte Compound-Literale sein. Diese Compound-Initialisierer durften nicht für auto-Structs und überhaupt nicht für Unions verwendet werden, sodass Stack-Structs und Unions nur Feld für Feld initialisiert werden konnten. Das Herausfallen am Ende von main war nicht der Sonderfall, den C99 später einführte; in C89 war der Rückgabewert undefiniert, obwohl die K&R-Beispiele implizieren, dass Implementierungen es als Erfolg behandelten.

Präprozessor und Operatoren hatten eigene Anachronismen. Compound-Zuweisungen wie += wurden als + und =, also zwei separate Tokens, gelesen. Der ältere =+-Stil war noch ein einzelnes Token, um Ausdrücke wie x = -1 eindeutig zu halten. String-Konkatenation gab es nicht, und die Operatoren # und ## fehlten, was viele Macros unmöglich machte; einige Implementierungen umgingen das, indem sie Macros innerhalb von String-Literalen expandierten. Vordefinierte Macros wie __DATE__, __FILE__, __LINE__, __TIME__ und __STDC__ gab es nicht, und doppelte Unterstriche-Identifikatoren waren nicht dokumentiert reserviert. Fehlende Direktiven waren #pragma, #error und #elif; es gab keinen defined-Operator, also prüfte man Existenz mit #ifdef und #ifndef. Eine schnelle Liste weiterer Unterschiede: Floats wurden in jeder arithmetischen Operation zu double befördert; der mittlere Ausdruck eines ternären Operators durfte kein Komma-Ausdruck sein; unäres Plus existierte nicht; es gab keine mit L markierten Wide-Character-Konstanten oder -String-Literale und kein wchar_t; und Enums existierten überhaupt nicht.