The author, Sebastian, spent time with the first edition of "The C Programming Language", the C89 rationale document, and the source of the 7th Edition Unix C compiler to document the pre-ANSI dialect now called K&R C. He notes that the language's much-maligned context-sensitive grammar was less absurd in its original setting: the first C compiler was single-pass, running on memory-starved hardware, so the parser was already tracking identifiers and scopes. Function-prototype scope did not exist yet, which removed the modern scoping headaches that make the grammar painful today.
The type system looked quite different. void was an ANSI invention; functions silently returned int, and char * served as the generic pointer because any pointer could be assigned to any other. long double did not exist, but long float was accepted as a synonym for double. There was no signed keyword; only unsigned was available, and it, along with short and long, acted as adjectives attached to int. Only one adjective was permitted, so long int was valid but long or unsigned short int were not. The order of specifier and adjective was free, so int long also parsed. Integer promotion was likewise unsettled: compilers diverged between "unsigned-preserving" and "value-preserving" rules. Under the former, -1 < (unsigned short)0 evaluated to false because operands were promoted to unsigned int; under the latter it was true because unsigned short values fit in signed int. The C89 committee chose value-preserving rules despite Unix compilers leaning the other way.
Constants and literals carried their own surprises. There was no U suffix, and in fact no unsigned constants at all. A non-decimal constant that fit in the range of unsigned int still received type int, so on a 16-bit int 0x8000 wrapped to negative. Octal constants happily accepted 8 and 9 as digits, so 078 and 0100 meant the same value, and the same held for octal escape sequences. Several escape sequences were missing: \a, \v, and \x did not exist. Invalid escapes were not errors; the backslash was silently dropped, which effectively made \" and \? work even before they were standardized. Floating constants were always double; no f or l suffixes existed.
Because int was intended to be the machine word, operations that today yield size_t or ptrdiff_t produced int instead. size_t and ptrdiff_t themselves are ANSI additions. An implied consequence was that no type could be larger than INT_MAX. const and volatile were absent too. C89 drafts did include a noalias qualifier, but Dennis Ritchie argued it was unworkable and it was removed before publication. The first printings of K&R 2nd edition still mentioned noalias, though later printings deleted it. String literals were defined as distinct mutable objects, a state of affairs that explains why literals still have type char * today: const did not exist, so adding it would have broken existing code.
A few reserved but unused keywords turned up. entry was reserved even though it never appeared in the grammar. Line continuations via a trailing backslash were restricted to character constants, string literals, and preprocessor directives; the ANSI committee allowed them everywhere because it was simpler. Function prototypes with typed parameter lists were an ANSI invention. In K&R C, function declarators used empty parentheses, and only definitions listed parameter names. That made it impossible to declare a variadic function, which is why the later requirement for an explicit printf prototype was a quiet breaking change. The K&R book's grammar for function definitions is also wrong: it describes a normal declarator followed by a parameter list, ignoring the usual spiral precedence of declarators, and the #line directive's grammar is wrong too, specifying a mandatory identifier where a string literal should appear and contradicting the prose that calls it optional.
Functions and pointers behaved differently. Array parameters decayed to pointers, as in modern C, but function parameters were not adjusted to pointers; they were simply disallowed. Function pointers could not be used directly in call expressions; an explicit dereference was required. The implicit adjustment of float parameters to double only happened in the K&R form, not in prototype form, and remained in the standard until C23 removed the K&R form entirely. The left operand of a shift was an exception to the usual arithmetic promotions: in standard C, 1 << 1L has type int, whereas K&R promoted the left operand to long.
Structs and unions were far more restricted. You could not assign to a struct or union lvalue, functions could neither take nor return them, and their initializers had to be braced compound literals. Those compound initializers could not be used for auto structs or for unions at all, so stack structs and unions could only be initialized field by field. Falling off the end of main was not the special case it became in C99; in C89 the return value was undefined, though the K&R examples imply implementations treated it as success.
The preprocessor and operators had their own anachronisms. Compound assignments like += were lexed as + and =, two separate tokens. The older =+ style was still a single token to keep expressions like x = -1 unambiguous. String concatenation did not exist, and the # and ## operators were absent, making many macros impossible; some implementations worked around this by expanding macros inside string literals. Predefined macros such as __DATE__, __FILE__, __LINE__, __TIME__, and __STDC__ were absent, and there was no documented reservation of double-underscore identifiers. Missing directives included #pragma, #error, and #elif; there was no defined operator, so existence checks used #ifdef and #ifndef. A quick list of other differences: floats were promoted to double in every arithmetic operation; the middle expression of a conditional could not be a comma expression; unary plus did not exist; there were no L-prefixed wide character constants or string literals and no wchar_t; and enums did not exist at all.