16

The way I see used most frequently


int main()
{
    int i;
    
    for (i=0; i<10; i++)
    {
        printf("hello\n");
    }
    
    return 0;
}

The way I’m used to


int main()
{
    for (int i=0; i<10; i++)
    {
        printf("hello\n");
    }
    
    return 0;
}

Questions

  • Is the first way not cumbersome?

  • What is the rationale behind it?

Background

I am helping my nephew with his coding; he is learning C. I’m able to understand some of it because of my experience with Bash.

He is in a third world country, and the teachers just throw books at the children and leave them to it. I suspect they don’t know the material themselves.

8
  • 9
    It's possibly a slur to imply that the low quality of teaching is because it's a third-world country. There are both good and bad teachers wherever you go. Feb 10, 2022 at 14:32
  • 16
    To be pedantic, the first mention of i in the first example doesn't "set" i (as your question title says), it declares i. You should understand the difference between declarations and assignments if you ever going to use an #include directive in your code (which means, pretty much from day 1 of C programming), because the header files you include almost always declare lots of variables and functions, but make no assignments or definitions.
    – The Photon
    Feb 10, 2022 at 15:56
  • 9
    The first version allows you to check the value of i after the loop. In the second example, the variable i disappears after execution leaves the loop. Feb 10, 2022 at 18:48
  • 11
    If your understanding of C is based only on your experience with Bash, then you should stop helping your nephew with C, now. Those are two completely different languages, and while both of them give the user far, far enough rope to shoot themselves in the feet, they do it in quite different ways, and what you learn in one translates really not well to the other.
    – ilkkachu
    Feb 11, 2022 at 8:23
  • 1
    Throwing good books is important though.
    – lalala
    Feb 13, 2022 at 7:54

5 Answers 5

71

Versions of C up to and including C89 (i.e. the language version standardised in 1989; note this was the last major revision to the C standard before 1999) allowed variables to be declared only at the beginning of a scope, which forces you into the style shown in your first snippet. If your nephew is using an older textbook (which I suspect is more likely in a third-world country), they may not have been updated to the new style.

There is no real reason to declare the variable outside the loop these days.

18
  • 12
    Sometimes people write code against old versions of C. Because it's often used on embedded platforms, and you have to use whatever compiler you can get.
    – Simon B
    Feb 10, 2022 at 12:37
  • 15
    @SimonB C99 allowed mixing declarations and statements not because other languages allow so and it is useful, but because (virtually?) all implementations did as a common conforming extension and it is very useful. Feb 10, 2022 at 12:44
  • 38
    Sometimes it can be useful to declare the iteration variable outside the loop so you can refer to the final value after the loop is done.
    – Barmar
    Feb 10, 2022 at 15:41
  • 16
    It is not only old textbooks. A lot of compilers for embedded systems, even in their latest versions, only support C89 (and thus forces the matter). GCC is not available for a lot of microcontrollers (microcontrollers in actual production use). An example is ST10F269. Feb 10, 2022 at 17:10
  • 3
    @Barmar But better to declare it inside the loop for the same reason, so you're consciously forced to save it off if you want to use it outside. Otherwise you can end up with really bad ideas like for (i=1; i<5; i++) { ... } for (;i<10;i++) { ... } and then someone changes the first loop carelessly and has fun finding out what went wrong. Feb 10, 2022 at 18:21
40

It's not just a question of sytle. The two ways of declaring the variable are not equivalent:

  • In the first case, i exists after the loop and you could use it. This is useful, for example if the number of iterations is not known in advance and relevant for the remaining of the algorithm.

  • In the second case i no longer exists after the for loop because of subtle scope rules. In most cases this is not a problem (it could even be a feature that avoids mistakes).

You’ll typically find the first form:

  • when the programmer wasn’t sure if the variable's content could be needed after the loop or not.
  • when the variable's content needs to be known after the loop
  • in old timers' code - those who learned C reading the 1978 edition of K&R - since in the early times of C it was required to define variables at the beginning of a block (and declaration within the loop statement was not allowed). (It includes also those who learned programming in the era of structured programming, where it was considered a good practice to declare variables ahead of the statements.)
  • by people who still imagine that declaring a separate i for each loop would matter for the performance. (Hint: it doesn’t).
14
  • 4
    "Btw, if these fellows manaed to adapt to the fact that parameter type is to be declared within the parentheses of the function, instead of after, they could have adapted to the just-in-time declaration." - Actually, C89 allows (and recommends) the new-style function declarations, but variable declaration was only unrestricted in C99. That leaves 10 years of C standard where people might learn one but not the other.
    – marcelm
    Feb 10, 2022 at 14:41
  • 13
    Part of why C89-style all-declarations-first coding is still common, is that the K&R book ("The C Programming Language"), which is still very good in most respects, and still commonly recommended to newcomers to the language, has not been revised since 1988.
    – zwol
    Feb 10, 2022 at 16:46
  • 2
    @marcelm no offense. As I was one of those people, I can tell the story as I lived it. At that time is was considered part of the good practices of structured programming to think first about the code, and declare the variables ahead of the statements of the block. You'll find plenty of books of that time that promote this practice, not only in C but also other structured languages such as Pascal or later Modula2. So when C became more permissive, many didn't use the permissivity, since it was also considered a bad habit. It took really a while to see that indeed this "good practice"...
    – Christophe
    Feb 10, 2022 at 18:12
  • 7
    I don't see what's subtle about the scope rules. I would just say, "because of scope rules" and leave it at that. But that's just me. Feb 10, 2022 at 20:28
  • 3
    @bta Counting variable declarations in the C source code tells you nothing about stack-space usage, even on embedded platforms, unless maybe you've somehow found yourself programming an MCU that has no registers. On highly memory-constrained platforms where you're concerned about stack utilization, the build toolchain has features to display the stack utilization per-function, which will be accurate and take into account any register-usage optimizations that the compiler may have performed, stack space re-use, values the linker may have put into non-volatile memory, etc. Feb 11, 2022 at 9:06
3

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.

2
  • Haha, Fortran and its crazy whitespace in names etc.... Funny that, instead of all the takeaways that one may well reasonably away from that, they picked that variable declarations should be mandatory (but initialization not). Somebody should have noticed that this was rather missing the real problem. Feb 11, 2022 at 16:18
  • 1
    @leftaroundabout It wasn't that FORTRAN allowed Whitespace in names, but rather that FORTRAN compilers filtered out all whitespace that wasn't between apostrophes.
    – supercat
    Feb 12, 2022 at 4:06
2

In principle, you can declare and initialise a variable in one step, or you can just declare it. In which case it has an indeterminate value. That’s the easy part. The hard question is when to use which.

Some general rules: You should declare a variable when it is needed, not earlier. You should assign a value close to where it is needed, not earlier. You shouldn’t assign a value that isn’t useful. So your first example goes against my first rule.

However, there is a bigger rule: You must write code that actually works. For example:

If (x > 0) {
    int I = 1;
} else {
    int I = 2;
}

This has the disadvantage that it doesn’t work: You have two variables named I, which both disappear at the closing }. So here you are forced to write:

int I;
If (x > 0) {
    I = 1;
} else {
    I = 2;
}

It’s ugly, but we can’t change that (well, your son can a few weeks down his course).

int I = 0;

Would be bad for two reasons: First, it goes against my third rule. Second, if you make a mistake and don’t assign 2 to I in the “else” branch, the compiler can’t warn that you haven’t assigned a value to I, because you did assign a useless value.

4
  • 1
    Well, some coding rules forbid defining variables without initializing them, in order to prevent UB from unintentionally using the variable uninitialized (e.g. by adding an else branch in a maintenance release that handles errors and does not set the variable, which is then returned). In this simple case one can define with initialization: int I = x > 0 ? 1 : 2; or the more obscure but perhaps faster int I = (x <= 0) + 1;... Feb 10, 2022 at 22:06
  • @Christophe. LOL... derp. That was just an added exercise for the student =P Feb 11, 2022 at 1:23
  • Peter, such a coding rule is totally misguided. Any decent compiler will tell you if there is a code path where an uninitialised value is used. But if you blindly assign a value, you take that ability away from the compiler. int day = 0; int month = 0; int year = 0; doesn't produce undefined behaviour, but it is a bug when you use it without assigning a correct value. Just the compiler can't tell you.
    – gnasher729
    Feb 11, 2022 at 10:36
  • You can also replace the original example with int I = 2; if (x > 0) { I = 1; } if you don't mind the double assignment in the x > 0 case. (And if that's the expected common case, it would totally make sense to reverse it to be int I = 1; if (x <= 0) { I = 2; } so that the if condition is expected to be false the majority of the time, minimizing instances of double assignments.)
    – FeRD
    Feb 13, 2022 at 6:00
-1

Beyond that loop, in general declaring a variable and not giving it a value was good(*) C style in cases where it would be assigned later or possibly never used. You communicated that fact to the reader, and saved a cycle by skipping a useless assignment (doing that wasn't considered a micro-optimization back then). For example, before a loop where v2 is meant to be the value at index i2:

int i2=-1, v2; // v2 unassigned, on purpose

v2 has no value before i2 is set to an in-range index, so this feels right. It's a little like using a nullable today.

Or not initializing variables we're just going to read into anyway:

int n;
scanf("%d", &n);

Modern languages don't allow the possibility of having an undefined variable at run-time, which is probably for the best. So modern coders see int v2=0; as a simple declaration and don't assume the 0 means anything other than "make the compiler happy", but in old C it stuck out as "I intentionally want this to start at 0". Back then you wouldn't write that unless you meant it.

(*) at some times, at some places, by some people, was considered good style. But using -1234 or something as a dummy value may have been just as common.

5
  • 5
    Some of those "modern" languages avoid uninitialized variables by assigning zero / null by default which potentially masks bugs, while others avoid uninitialized use by limited flow analysis, which only allows use if they can prove prior assignment ("definitely assigned"). Feb 11, 2022 at 1:28
  • 3
    The main reason you more often see int v2=0; is that C99 allows you to delay declaration until you have a value to assign. But in code written by people that understand C, you'll still see just int v2; if they're about to pass its address to something that will initialize it (like scanf). int v2=0; initialization before assigning some other value would definitely stick out to me in modern C. (I'm no fossil, but I wouldn't consider myself "modern"). Feb 11, 2022 at 2:17
  • 3
    And those who understand C will check the return value from scanf() to determine whether it actually assigned a value (that's a bug I see far too often over in Code Review). Feb 11, 2022 at 8:21
  • @PeterCordes I actually had to look it up to remember how scanf works. Feb 11, 2022 at 8:23
  • 1
    @TobySpeight: If you check the return value of scanf, you're doing something wrong (i.e. using scanf in the first place).
    – supercat
    Feb 12, 2022 at 4:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.