El autor, Sebastian, se sumergió en la primera edición de "The C Programming Language", el documento de justificación del C89 y el código fuente del compilador C de la 7ª edición de Unix para documentar el dialecto pre-ANSI conocido como K&R C. Señala que la mucho criticada gramática sensible al contexto resultaba menos absurda en su entorno original: el primer compilador de C era de una sola pasada en máquinas con poca memoria, así que el analizador ya rastreaba identificadores y ámbitos. El ámbito de prototipos de función aún no existía, lo que evitaba los dolores de cabeza modernos que hacen la gramática tan difícil hoy.
El sistema de tipos era muy distinto. void era una invención de ANSI; las funciones devolvían int en silencio, y char * servía como puntero genérico porque cualquier puntero podía asignarse a cualquier otro. No existía long double, pero long float era sinónimo de double. No había palabra clave signed; solo unsigned estaba disponible, y junto con short y long actuaba como adjetivo de int. Solo se permitía un adjetivo, así que long int era válido pero long o unsigned short int no. El orden era libre, por lo que int long también era válido. La promoción de enteros también estaba sin estandarizar: los compiladores se dividían entre las reglas "unsigned-preserving" y "value-preserving". Con la primera, -1 < (unsigned short)0 era falso porque los operandos se promovían a unsigned int; con la segunda, era verdadero porque los valores de unsigned short caben en signed int. El comité de C89 eligió value-preserving a pesar de que los compiladores Unix tendían hacia lo contrario.
Las constantes y literales tenían sus propias sorpresas. No existía el sufijo U, y de hecho no había constantes unsigned. Una constante no decimal que cupiera en el rango de unsigned int recibía tipo int, así que 0x8000 en un int de 16 bits se envolvía a negativo. Las constantes octales aceptaban 8 y 9 como dígitos, por lo que 078 y 0100 representaban el mismo valor, y lo mismo aplicaba a las secuencias de escape octales. Faltaban varias secuencias de escape: \a, \v y \x no existían. Las secuencias inválidas no eran error; la barra invertida se eliminaba en silencio, lo que hacía que \" y \? funcionaran de facto antes de estandarizarse. Las constantes de coma flotante siempre eran double; no había sufijos f ni l.
Como int estaba pensado como la palabra de máquina, las operaciones que hoy devuelven size_t o ptrdiff_t devolvían int. size_t y ptrdiff_t son añadidos de ANSI. Una consecuencia era que ningún tipo podía superar INT_MAX. const y volatile también faltaban. Los borradores de C89 incluían un calificador noalias, pero Dennis Ritchie argumentó que no funcionaba y se eliminó antes de publicar. Las primeras impresiones de la segunda edición de K&R aún mencionaban noalias, aunque las posteriores lo quitaron. Los literales de cadena se definían como objetos distintos y mutables, lo que explica por qué hoy tienen tipo char *: const no existía, así que añadirlo habría roto el código existente.
Aparecían algunas palabras clave reservadas pero no utilizadas. entry estaba reservada sin aparecer nunca en la gramática. Las continuaciones de línea con barra invertida al final solo estaban permitidas en constantes de carácter, literales de cadena y directivas del preprocesador; el comité ANSI las permitió en cualquier lugar porque era más simple. Los prototipos de función con listas de parámetros tipados eran una invención de ANSI. En K&R C, los declaradores de función usaban paréntesis vacíos, y solo las definiciones listaban nombres de parámetros. Eso hacía imposible declarar funciones variádicas; por eso el requisito posterior de un prototipo explícito para printf fue un cambio silencioso. La gramática de definiciones de función del libro K&R también es incorrecta: describe un declarador normal seguido de una lista de parámetros, ignorando la precedencia en espiral habitual de los declaradores. La gramática de #line también es errónea, especificando un identificador obligatorio donde debería ir un literal de cadena, contradiciendo la prosa que lo describe como opcional.
Las funciones y punteros se comportaban de forma distinta. Los parámetros array se convertían en punteros como en C moderno, pero los parámetros función no se ajustaban a punteros; simplemente estaban prohibidos. Los punteros a función no podían usarse directamente en expresiones de llamada; se requería una desreferencia explícita. El ajuste implícito de parámetros float a double solo ocurría en la forma K&R, no en la forma de prototipo, y permaneció en el estándar hasta que C23 eliminó la forma K&R por completo. El operando izquierdo de un desplazamiento era una excepción a las promociones aritméticas habituales: en C estándar, 1 << 1L tiene tipo int, mientras que K&R promovía el operando izquierdo a long.
Las structs y unions eran mucho más restrictivas. No se podía asignar a un valor-izquierda de struct o union, las funciones no podían recibirlas ni devolverlas, y sus inicializadores debían ser literales compuestos entre llaves. Esos inicializadores compuestos no podían usarse para structs auto ni para unions, así que las structs de pila y las unions solo podían inicializarse campo por campo. Caer al final de main no era el caso especial que C99 introdujo después; en C89, el valor de retorno era indefinido, aunque los ejemplos de K&R implican que las implementaciones lo trataban como éxito.
El preprocesador y los operadores tenían sus propios anacronismos. Las asignaciones compuestas como += se leían como + y =, dos tokens separados. El antiguo estilo =+ seguía siendo un solo token, para mantener expresiones como x = -1 sin ambigüedad. No existía la concatenación de cadenas, y los operadores # y ## estaban ausentes, lo que hacía imposibles muchas macros; algunas implementaciones lo evitaban expandiendo macros dentro de literales de cadena. Las macros predefinidas como __DATE__, __FILE__, __LINE__, __TIME__ y __STDC__ no existían, y los identificadores que comienzan con doble guion bajo no estaban documentados como reservados. Faltaban las directivas #pragma, #error y #elif; no había operador defined, así que se comprobaba la existencia con #ifdef y #ifndef. Una lista rápida de otras diferencias: los flotantes se promovían a double en todas las operaciones aritméticas; la expresión media de un condicional ternario no podía ser una expresión coma; no existía el más unario; no había constantes de carácter anchas ni literales de cadena con prefijo L, ni wchar_t; y los enums no existían en absoluto.