38

In modern C, you may place variable declarations between statements:

do_something();
int x;
x = something_else();

However, older C compilers required that variables are declared before all statements:

int x;
do_something();
x = something_else();

(Page 33 of The C Programmer's Handbook, AT&T Bell Laboratories, 1984 states "Within a block, all declarations must precede the first statement." Page 223 of The C Programming Language, Kernighan & Ritchie, 2nd edition 1988 defines a "compound statement" as { declaration-listopt statement-listopt }.)

What was the reason (whether philosophical or practical) that early C compilers could not allow declarations between statements?

You need to be much more specific than saying "compilers were dumb". For example, the following code was supported by these same compilers:

do_something();

{
    int x;
    x = something_else();
}
20
  • 15
    This is just a guess, so I'm not posting it as an answer, but it wouldn't surprise me if the answer is a mix of "easier to lay out the stack frame in a single-pass compiler if the variable declarations come first" and "it was more common to see languages in the vein of Pascal where functions and procedures aren't single-block constructs and sequester variable declarations away from the code in a special block at the top". Wikipedia has an un-cited claim that the latter was motivated by the former.
    – ssokolow
    Oct 26, 2021 at 20:11
  • 27
    Declarations should always be at the top of the block, AND stay off my grass :)
    – Brian H
    Oct 26, 2021 at 20:16
  • 12
    @BrianH It's "get off my lawn" </pedant> :P
    – ssokolow
    Oct 26, 2021 at 20:18
  • 7
    @BrianH I was more concerned with the "lawn" vs. "grass" than the "get off " vs. "stay off".
    – ssokolow
    Oct 26, 2021 at 21:39
  • 11
    @jamesqf, That's a question of taste, and I know people—good people, some of 'em—who might disagree with yours. They might ask, how does it make my code more "readable" if I write double x; ten lines above the place where I write x = ...; instead of just writing one line, double x = ...; ? Oct 27, 2021 at 17:43

6 Answers 6

39

Some early compilers, such as the ones described in the 1974 C reference manual, required that all automatic object declarations within a function precede the first executable code therein. Imposing this restriction made it possible for compilers to know the stack-relative offset of a function's arguments before it had to process any code that used them. Easing this restriction increased the amount of compiler complexity necessary to avoid gross inefficiencies, though the amount of added complexity could vary considerably depending upon the target platform, downstream build tools (e.g. sophistication of the assembler and linker), and tolerance for inefficient code.

Many compilers targeted platforms where some amount of interleaving between declarations and code could be accommodated reasonably conveniently and efficiently, and thus accepted constructs that the earliest compilers could not. Allowing arbitrary intermixing of declarations and other statements, however, can cause some severe complications if execution branches from a spot within a variable's scope to a spot that is within its lifetime but precedes its scope. Requiring that all declarations within a block precede the first statement within that block is more restrictive than would be necessary to guard against that, but is nonetheless effective for that purpose.

As far as your third example (a new declaration within a nested block) any branch targets that precede the nested int x declaration would be outside the block containing x, and thus could not be reached without ending the lifetime of x. Further, it would not be unreasonable for compilers to generate code that adjusts the stack pointer when entering or leaving a block containing automatic object declarations, since doing so would reduce the stack requirements of function calls that are performed within the function but not within the block.

Note that the C89 Standard deliberately excludes constructs that were widely supported, but for which the authors of the Standard did not think support would be universally practical. I think the authors of the Standard should have been willing to recognize that some constructs should be broadly but not necessarily universally supported, and recognized a category of "limited" implementations which would be required to reject programs they couldn't process usefully, but would be required to meaningfully process programs they accept. If someone is trying to run a C compiler on a machine with 48K of RAM, a compiler that omits features the person can do without may be more useful than one which tries to include everything that should be supported by compilers running on larger systems.

1
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Chenmunka
    Oct 29, 2021 at 17:11
18

TLDR: most languages in 1969/70 had declarations first.

BCPL had data declarations first within a block, and while the reference manuals for B on dmr's (preserved) website only specify declaration before use, (corrected) the tutorial specifically says "all declarations must precede executable statements" (in a function). Standard FORTRAN required 'specification' statements (if used at all; they could be omitted for scalars using the implicit typing, as davidbak reminded me) first in a subprogram, and COBOL had them in a 'division' before any code although after some environmental stuff (both still do for a given program-unit, although both now allow nested program units with their own declarations after the code). Pascal not only required declarations before code, it ordered them in subgroups: first constants, then types, then variables. Algol60 had declarations first in a block but allowed nested blocks (both like C), and IINM Jovial also; Algol68 relaxed this, along with many other things, but was widely regarded as unimplementable on small machines like the PDP-11 (and later Interdata) used for Unix/C. Somewhat similarly, PL/1 allowed declarations anywhere, but was the language of Multics, the system against whose bloat and cost Unix and C rebelled -- and even in PL/1 it was generally regarded as better style to put declarations at the top of a procedure or at least block.

BASIC of the day declared only arrays, and APL didn't have any declarations that I recall. LISP was, well, LISP; you wouldn't look there for ideas on designing something like C. And IIRC a popular textbook of the day on programming basics was "Data Structures Plus Algorithms" -- note the order.

"If it ain't broke, don't fix it."

28
  • 1
    It's been decades since I learned FORTRAN II (my first language) but it didn't even require declarations at all, right? It had implicit typing integer vs float by first letter of the variable name. A common error, IIRC, was misspelling an identifier = new var. (Fortunately, there was none of this upper-case/lower-case nonsense we have today to worry about: your punch cards were upper case only.) But even more important: FORTRAN didn't require a stack to allocate local subprogram variables and didn't have anything to clean up afterward: No recursion allowed! No stack. Everything "static".
    – davidbak
    Oct 28, 2021 at 2:27
  • 1
    @another-dave Not sure about the pre-standardisation Fortrans but standard Fortran of any version I am aware of require all declarations to precede all executable statements in the program unit. Oct 28, 2021 at 13:44
  • 1
    @another-dave It is a numbered constrain't in the standard. That means that the compiler is required to diagnose it as an error. To allow declarations inside a program unit Fortran 2008 introduced a block construct (something like{ } in C). There is no technical reason why it wouldn't be possible, it was a design choice by X3J3. Oct 28, 2021 at 16:06
  • 1
    @davidbak: Trigraphs in comments aren't as bad as trigraphs in string and character literals. Instead of supporting that abomination, the Standard should have specified a means of indicating that a particular character outside the C character set should behave as a backslash, except that escaping that character should yield itself (escaping a forward slash should yield a backslash regardless of whether the escape character is a backslash). Unless an execution platform has characters that aren't in the source character set, there's no meaningful way to process trigraphs...
    – supercat
    Oct 28, 2021 at 21:23
  • 1
    @davidbak There really uses the word "standardization" too freely. At least in the context of traditional languages like Fortran, Algol, COBOL, Scheme and so on. What is understood by standardization of Fortran is summarized in dl.acm.org/doi/abs/10.1145/1500774.1500877 IBM of course played a very important role in Fortran 66 formulation, but there were already several other vendors by the early 1960s with various extensions and incompatibilities. Oct 28, 2021 at 22:14
9

Automatic variables like those are often stored on the stack. This means for each function, the first thing the generated code (at the time, assembly, not machine code) does is reserve space on the stack. To do that, it needs to know all the variables and their sizes.

Modern compilers will without trouble go through all the source code of the function and find out all the variables, and then generate all the code (and a lot more, including optimisation).

But if you want to build the simplest compiler possible, running on systems with very little memory, being able to generate the assembly as you process input is a big advantage (rather than storing a lot of things in memory and then output the assembly).

To do that, you need to have all those declarations before the first line of code. This way, your compiler goes like this:

  • process variable declarations: make note of their names, sizes, (constant) initialisers, and the offset you will place them at on the stack
  • output stack adjustment
  • output initialisation
  • start processing statements and output assembly for each statement as you process it

Of course nowadays with GiB of RAM everywhere this seems pointless, but at the time, anything you could read-process-output-forget was a big bonus.

1
  • The benefits of such simplification is dwarfed by the complexity of optimizers that spend more time trying to "optimize" large parts of many programs than would have been spent executing them naively (in many programs, the majority of of the code will execute on average less than twice each time the program executes) but I think the maintainers of the C Standard have lost sight of one of the design goals of C.
    – supercat
    Oct 28, 2021 at 19:37
7

The C language was not suddenly specified first and then made. C language was developed based on B language, which was a simplification on BCPL systems programming language, and C evolved a few years with new features that were needed and it became popular.

These languages (BCPL, B, C) were used to make the system level tools and utilities and thus were compiled on very small systems and thus needed very simple and they were only needed to support strict lexical parsing to be able to do what compilers do - parse structured text and output binary code. They were intended to have some medium level language so it was not necessary to write everything in system specific assembly language from scratch.

This is in contrast to other languages like Algol-68, which was an application programming language to produce higher level programs.

When C became popular, people also started to use it for applications, so at that point C programs became more and more complex and only at this point most likely people who were application programmers started to miss the feature.

Over the times computers got faster, got more memory, got improved language parser libraries, etc, so basically nothing prevented this but compiler implementations and code were typically adhering to the de facto standard, which later on became the C89 standard.

Many compilers extended beyond the standard and supported this feature as a non-standard extension before the feature was officially included in the C99 standard.

It was included in C99 because it was already found as a useful feature in Algol 68 and C++ programming languages.

The problem with standards is that even if compilers with extensions existed before, you had to conform to standards to be able to have portable code. If you used extensions, the scope could be limited as different compilers might have divergent implementations how to do things.

For example there might be contractual rules or regulations that mandate how you should write compliant code say for example medical devices or military equipment, and they might not allow custom extensions or might be limited to one compiler that has been validated for the work.

If you were required to write safety critical code using MISRA guidelines, they only included support for C99 in 2012, as the previous standard in 2004 stated that there were no commercial embedded compilers supporting C99 available.

So even if technically possible and available, you may not be able to use extensions outside standards for some reason even if you wanted to, or you may not even want to, if you want to support older systems with older compilers that have no support for C99 or similar extensions.

26
  • 3
    C was invented in 1974. The first standard wasn't published until 1989, and its purpose was merely to describe features that were common among all existing C dialects, and not to imply that implementations that could support useful constructs beyond those mandated shouldn't do so when practical.
    – supercat
    Oct 26, 2021 at 20:40
  • 3
    I can stand with all said in this answer (even upvoted). Just, it doesn't answer the question why some compilers were restricted (as said by the OP), or why it was put into the papers in the first place. It is kind of arguing in hindsight about today, isn't it?
    – Raffzahn
    Oct 26, 2021 at 21:52
  • 1
    It is impossible to write a non-trivial Strictly Conforming C Program for a freestanding implementation, but by definition every piece of source text that is accepted by at least one conforming C compiler somewhere in the universe is a "Conforming C Program". As such, the notion of conforming to the Standard isn't really meaningful, at least with respect to freestanding implementations or programs for them.
    – supercat
    Oct 26, 2021 at 22:30
  • 1
    @supercat Ansi C included several changes from the previous C "standard" (i.e. the language described in K&R first edition). Most notably, it included the modern function definition and prototyping syntax.
    – JeremyP
    Oct 27, 2021 at 7:55
  • 1
    BTW, to give you an idea how small the compilers still were at the time, I was running the C compiler from a 360K floppy disk on a IBM PC. Source code was on a second floppy disk; I had to change disks to run the linker.
    – JDługosz
    Oct 27, 2021 at 23:13
4

Early C had to be as small as possible!

Remember that Version 7 Unix, released in 1979, and ran on the DEC PDP-11. The PDP-11 had only a 16-bit address bus, and could only address 64KB of memory.

Quite simply, the early C compiler had to be as simple as possible! There was not room for anything that might complicate the compiler.

Early C was an elegant and practical little language. Served well as "high level assembly". But of necessity had to be light on features.

13
  • 4
    I'm not sure I understand how "declarations come first" translates to "small code size". In either case, you're basically parsing a line of tokens and sticking whatever you come up into a linked list or other collection. Oct 27, 2021 at 7:46
  • 5
    @OmarL single pass compiler says what?
    – user253751
    Oct 27, 2021 at 8:39
  • PDP-11 ISA has 16-bit logical addresses (from 16-bit registers), but even the earliest machines had an 18-bit bus (Unibus) -- which without memory-management option could handle only 56KB memory because 8KB space was used for I/O. Per a recap paper by Ritchie the initial machine (an 11/20) had only 24KB. By the 1978 BSTJ paper it was an 11/70 (which had KSU-I&D MM and 22-bit MassBus nominally capable of 4G) fitted with 768K (shared by multiple users). Oct 27, 2021 at 8:47
  • 1
    @user253751 what? Oct 27, 2021 at 9:51
  • 1
    Specifically, the infamous GOTO statement would be harder to implement. 1. If you transfer control to a different location within the same block, that is trivial. 2. If you transfer control from within one block to another, then you have to account for differences in the variables on the stack. 3. If you allow variables to be declared ad-hoc, then that must be accounted for. How do you do this? Should the compiler implicitly move all declarations to the beginning of the block? Should it create an implicit scope block? As you can see, it's more complicated. Oct 28, 2021 at 12:09
1

That's what the language definition specified, at least in K&R,"The C Programming Language", first edition (1978).

The BNF for a "compound statement" was (K&R, Appendix A, 9.2)

compound statement :
   { declaration-list statement-list }

(ignoring some details)

and for a function body (K&R, Appendix A, 10.1)

function-body :
   declaration-list compound-statement

thus defining that declarations preceded statements.

This followed not just BCPL and B, but also other "modern" block-structured languages like the Algols and Pascal, so it was the way programmers expected to work, and contemporary compilers could easily handle it.

19
  • It would be interesting to know whether compilers of that era would more typically allocate and release stack space on entering/exiting a block, or would typically consolidate allocations? If the former, it's easy to see how block scoping could allow programs to run with less stack space than would otherwise be needed, though allowing goto to enter and leave scopes which allocate and release stack space would add significant cost to a single-shot compiler, since any forward jump would either need to include a stack-adjustment instruction that would often adjust the stack by zero bytes...
    – supercat
    Oct 29, 2021 at 18:34
  • ...or else forward gotos that enter or leave blocks would need to target a springboard which is placed after the rest of the function's code, and which would adjust the stack before jumping back into the main line code. Both of those approaches would be possible, but would increase compiler complexity versus having programs place all automatic object declarations before any executable code.
    – supercat
    Oct 29, 2021 at 18:36
  • @supercat my "goto" (sorry) for this is Bornat's "Understanding and writing compilers" which I still think is excellent (if a bit ICL-1900 oriented!). Indeed, function/block prologues built stack frames, holding both arguments (for functions) and locals ("auto" to C people). But I can't remember how Bornat handled non-local gotos. Oct 29, 2021 at 20:14
  • The question already cites K&R, and is about why they chose such.
    – DrSheldon
    Oct 29, 2021 at 21:11
  • 1
    @DrSheldon it cites a much later edition of K&R. Oct 29, 2021 at 22:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .