I was recently reading the C99 rationale (because why the fuck not) and I was intrigued by some comments that certain features were retired in C89 , so I started wondering what other weird features existed in pre-standard C. Naturally, I decided to buy a copy of the first edition of The C Programming Language , which was published in 1978.

Here are the most interesting things I found in the book:

The original hello, world program appears in this book on page 6.

In C, the program to print “hello, world” is main() { printf("hello, world

"); } Note that return 0; was not necessary.

Exercise 1-8 reads:

Write a program to replace each tab by the three-column sequence > , backspace, - , which prints as ᗒ, and each backspace by the similar sequence ᗕ. This makes tabs and backspaces visible. … Wait, what? This made no sense to me at first glance. I guess the output device is assumed to literally be a teletypewriter, and backspace moves the carriage to the left and then the next character just gets overlaid on top of the one already printed there. Unbelievable!

Function definitions looked like this: power(x, n) int x, n; { ... return(p); } The modern style, in which the argument types are given within the parentheses, didn’t exist in K&R C. The K&R style is still permitted even as of C11, but has been obsolete for many years. (N.B.: It has never been valid in C++, as far as I know.) Also note that the return value has been enclosed in parentheses. This was not required so the authors must have preferred this style for some reason (which is not given in the text). Nowadays it’s rare for experienced C programs to use parentheses when returning a simple expression.

Because of the absence of function prototypes, if, say, you wanted to take the square root of an int , you had to explicitly cast to double , as in: sqrt((double) n) (p. 42). There were no implicit conversions to initialize function parameters; they were just passed as-is. Failing to cast would result in nonsense . (In modern C, of course, it’s undefined behaviour.)

void didn’t exist in the book (although it did exist at some point before C89; see here, section 2.1). If a function had no useful value to return, you just left off the return type (so it would default to int ). return; , with no value, was allowed in any function. The general rule is that it’s okay for a function not to return anything, as long as the calling function doesn’t try to use the return value. A special case of this was main , which is never shown returning a value; instead, section 7.7 (page 154) introduces the exit function and states that its argument’s value is made available to the calling process. So in K&R C it appears you had to call exit to do what is now usually accomplished by returning a value from main (though of course exit is useful for terminating the program when you’re not inside main ).

Naturally, since there was no void , void* didn’t exist, either. Stroustrup’s account (section 2.2) appears to leave it unclear whether C or C++ introduced void* first, although he does say it appeared in both languages at approximately the same time. The original implementation of the C memory allocator, given in section 8.7, returns char* . On page 133 there is a comment: The question of the type declaration for alloc is a vexing one for any language that takes its type-checking seriously. In C, the best procedure is to declare that alloc returns a pointer to char , then explicitly coerce the pointer into the desired type with a cast. (N.B.: void* behaves differently in ANSI C and C++. In C it may be implicitly converted to any other pointer type, so you can directly do int* p = malloc(sizeof(int)) . In C++ an explicit cast is required.)

It appears that stdio.h was the only header that existed. For example, strcpy and strcmp are said to be part of the standard I/O library (section 5.5). Likewise, on page 154 exit is called in a program that only includes stdio.h .

Although printf existed, the variable arguments library ( varargs.h , later stdarg.h ) didn’t exist yet. K&R says that printf is … non-portable and must be modified for different environments . (Presumably it peeked directly at the stack to retrieve arguments.)

The authors seemed to prefer separate declaration and initialization. I quote from page 83:

In effect, initializations of automatic variables are just shorthand for assignment statements. Which form to prefer is largely a matter of taste. We have generally used explicit assignments, because initializers in declarations are harder to see. These days, I’ve always been told it’s good practice to initialize the variable in the declaration, so that there’s no chance you’ll ever forget to initialize it.

Automatic arrays could not be initialized (p. 83)

The address-of operator could not be applied to arrays. In fact, when you really think about it, it’s a bit odd that ANSI C allows it. This reflects a deeper underlying difference: arrays are not lvalues in K&R C. I believe in K&R C lvalues were still thought of as expressions that can occur on the left side of an assignment, and of course arrays do not fall into this category. And of course the address-of operator can only be applied to lvalues (although not to bit fields or register variables). In ANSI C, arrays are lvalues so it is legal to take their addresses; the result is of type pointer to array . The address-of operator also doesn’t seem to be allowed before a function in K&R C, and the decay to function pointer occurs automatically when necessary. This makes sense because functions aren’t lvalues in either K&R C or ANSI C. (They are, however, lvalues in C++.) ANSI C, though, specifically allows functions to occur as the operand of the address-of operator.

The standard memory allocator was called alloc , not malloc .

It appears that it was necessary to dereference function pointers before calling them; this is not required in ANSI C.

Structure assignment wasn’t yet possible, but the text says [these] restrictions will be removed in forthcoming versions . (Section 6.2) (Likewise, you couldn’t pass structures by value.) Indeed, structure assignment is one of the features Stroustrup says existed in pre-standard C despite not appearing in K&R (see here, section 2.1).

In PDP-11 UNIX, you had to explicitly link in the standard library: cc ... -lS (section 7.1)

Memory allocated with calloc had to be freed with a function called cfree (p. 157). I guess this is because calloc might have allocated memory from a different pool than alloc , one which is pre-zeroed or something. I don’t know whether such facilities exist on modern systems.

Amusingly, creat is followed by [sic] (p. 162)

In those days, a directory in UNIX was a file that contains a list of file names and some indication of where they are located (p. 169). There was no opendir or readdir ; you just opened the directory as a file and read a sequence of struct direct objects directly. Example is given on page 172. You can’t do this in modern Unix-like systems, in case you were wondering.

There was an unused keyword, entry , said to be reserved for future use . No indication is given as to what use that might be. (Appendix A, section 2.3)

Octal literals were allowed to contain the digits 8 and 9 , which had the octal values 10 and 11 , respectively, as you might expect. (Appendix A, section 2.4.1)

All string literals were distinct, even those with exactly the same contents (Appendix A, section 2.5). Note that this guarantee does not exist in ANSI C, nor C++. Also, it seems that modifying string literals was well-defined in K&R C; I didn’t see anything in the book to suggest otherwise. (In both ANSI C and C++ modifying string literals is undefined behaviour, and in C++11 it is not possible without casting away constness anyway.)

There was no unary + operator (Appendix A, section 7.2). (Note: ANSI C only allows the unary + and - operators to be applied to arithmetic types. In C++, unary + can also be applied to pointers.)

It appears that unsigned could only be applied to int ; there were no unsigned chars, shorts, or longs. Curiously, you could declare a long float ; this was equivalent to double . (Appendix A, section 8.2)

There is no mention of const or volatile ; those features did not exist in K&R C. In fact, const was originally introduced in C++ (then known as C With Classes ); this C++ feature was the inspiration for const in C, which appeared in C89. (More info here, section 2.3.) volatile , on the other hand, originated in C89. Stroustrup says it was introduced in C++ [to] match ANSI C ( The Design and Evolution of C++ , p. 128)

The preprocessing operators # and ## appear to be absent.

The text notes (Appendix A, section 17) that earlier versions of C had compound assignment operators with the equal sign at the beginning, e.g., x=-1 to decrement x . (Supposedly you had to insert a space between = and - if you wanted to assign -1 to x instead.) It also notes that the equal sign before the initializer in a declaration was not present, so int x 1; would define x and initialize it with the value 1. Thank goodness that even in 1978 the authors had had the good sense to eliminate these constructs… :P

A reading of the grammar on page 217 suggests that trailing commas in initializers were allowed only at the top level. I have no idea why. Maybe it was just a typo.