As several other people have mentioned, this style of variable declaration used to be mandatory in the earliest versions of C. The original reason for this was to make it simpler to write a C compiler.
First, declaring all variables once, at the start of the block, makes it possible to write a single-pass, non-optimizing compiler that allocates the memory for all the automatic variables on the stack (and all the static ones in the data segment). Kernighan and Ritchie did not want to make their compiler implement either an extra pass to make a complete list of all variables used within every block, or a type-inference algorithm (like the one Curry had developed decades before, but only published in 1969). Since they wanted C variables to be statically-typed, this necessitated a full declaration of every variable before use. (Functions, on the other hand, were not statically-typed in K&R C, and therefore did not have prototypes yet.) Putting all these declarations at the start of the block allowed the compiler to calculate how much stack space to allocate and what the address of every variable would be, before it began to generate code that used it.
In those days, it was considered the programmer’s responsibility to hand-optimize code. It was assumed that a C compiler would translate the statements the programmer wrote more or less literally, without doing major refactoring behind the programmer’s back. K&R even described C, in their book, as “not a high-level language.” At that time, it was considered good practice to rewrite tail-recursive algorithms as iterative loops, Re-using variables to hold different values at different times would actually save memory. The language has a
register keyword because programmers were supposed to give the compiler hints about how to allocate registers. Programmers even unrolled loops by hand, or with clever hacks such as Duff’s device.
Another reason was that the dominant paradigm of programming at the time considered it risky not to require variable declarations before use. One well-known example at the time C was being designed was an accident caused by a bug in a Fortran program, where the programmer had meant to write
DO 31 = 3,1 (a loop that repeated itself three times), but instead had the typo
DO 31 = 3.1. One of the recommendations of the investigation was that the design of the language was partly at fault. Since the Fortran compiler ignored whitespace, had no reserved keywords and allowed variables to be implicitly defined by assigning to them, it interpreted this line as creating a new variable named
DO31, of type
REAL, and setting its value to
3.1. In the early 1970s, when C was invented, it was therefore considered safer to require explicit variable declarations, before the code, so it would be easy to find find where any variable was declared and fix typos in the name of an identifier. (Fortran itself, in the 1970s, got a command to disable the implicit-variable feature.)
This of course created a new category of bug, using a variable after it was declared but before it was initialized. When C was designed, it was considered sufficient to warn programmers not to do that. It was not yet considered necessary to make programmers initialize a variable as part of the declaration, or even allow them to if the value depended on a previous computation. Once this was added, the language also needed to allow variable declarations after any statement.
Modern compilers now use a more sophisticated register-allocation algorithm such as Chaitin’s algorithm. However, it can still be useful to compile the old-fashioned way, for some purposes: if you disable optimization on GCC, you will be guaranteed to get an executable that cooperates well with the GDB debugger. Every variable named in the source code will have a unique location in memory that can be watched. Every line of code corresponds to an instruction address that you can insert a breakpoint at and single-step through. Try this with
-O3 -fomit-frame-pointer, and it is very likely that your attempts to put a breakpoint on a statement and inspect a variable at that line will fail, because the compiler completely refactored the code and optimized the variable away.