scoping within for loops

S

Stephen J. Fromm

What are the scoping rules for a for loop?

Example:

int i;
for(i = 0; i < 10; i++) {
int j;

/* more code here */

}

In this example, is the scope (a) "renewed" upon each iteration of
loop, or (b)common across all iterations? (My impression from
compiling code is "(a)").

This is in reference to scoping within blocks: one can write, absent
any control structure,
{
int j = 7;
/* more code */
}
and the lifetime of j is confined to the { }s, if I recall correctly.
With this in mind, (a) is like
int i = 0;
{
int j;
/* more code */
}
/* test i */
{
int j; /* New "instance" of j */
/* more code (again) */
}
....

whereas (b) is like
int i = 0;
{
int j; /* only 1 instance of j */
/* more code */

/* test i */

/* more code */
}

TIA,

sjfromm
 
D

Derk Gwen

(e-mail address removed) (Stephen J. Fromm) wrote:
# What are the scoping rules for a for loop?
#
# Example:
#
# int i;
# for(i = 0; i < 10; i++) {
# int j;
#
# /* more code here */
#
# }
#
# In this example, is the scope (a) "renewed" upon each iteration of
# loop, or (b)common across all iterations? (My impression from
# compiling code is "(a)").

j is local to the surrounding {...}. It's value is undefined from the
beginning of the block until initialised or assigned a value. That's
all you should really concern yourself with.

for (...)
if (...) {
int j;
}else {
float k;
}

It is possible j and k will be assigned the same place in the stack frame.
With each iteration that slot in the stack frame might hold the previous
value of j, of k, or of anything else.

# and the lifetime of j is confined to the { }s, if I recall correctly.
# With this in mind, (a) is like
# int i = 0;
# {
# int j;
# /* more code */
# }
# /* test i */
# {
# int j; /* New "instance" of j */
# /* more code (again) */
# }

In non-iterative code, the variables in disjoint blocks may be overlapped or
disjoint at the compiler's discretion. There no universally correct answer.
Within a loop a compiler will assign a variable to the same stack frame offset,
but that's for a practical reason: otherwise the stack frame could grow
with each iteration and overflow the stack when there is no need to do so.
It's not something you should depend on though, since other code might use
the same slot without warning you.
 
E

E. Robert Tisdale

Stephen said:
What are the scoping rules for a for loop?

Example:
> cat main.c
#include<stdio.h>

int main(int argc, char* argv[]) {
const int n = 10;
for (int i = 0; i < n; ++i) {
fprintf(stdout, "%d = i\n", i);
int i = 0;
fprintf(stdout, "%d = i\n", i);
}
return 0;
}
> gcc -Wall -std=c99 -pedantic -o main main.c
> ./main
0 = i
0 = i
1 = i
0 = i
2 = i
0 = i
3 = i
0 = i
4 = i
0 = i
5 = i
0 = i
6 = i
0 = i
7 = i
0 = i
8 = i
0 = i
9 = i
0 = i
 
S

Stephen J. Fromm

E. Robert Tisdale said:
Stephen said:
What are the scoping rules for a for loop?

Example:
cat main.c
#include<stdio.h>

int main(int argc, char* argv[]) {
const int n = 10;
for (int i = 0; i < n; ++i) {
fprintf(stdout, "%d = i\n", i);
int i = 0;
fprintf(stdout, "%d = i\n", i);
}
return 0;
}

Thanks for your reply.

Very interesting example. While the "first i" and "second i" occur
within the same block, your example makes it appear there are is no
scoping conflict. Is this because
(a) scoping is largely dependent on declarations, and declarations
only extend downwards (here, the declarations *don't* occur in the
same block, and the "second i" is declared after the first one is used
but not declared), or
(b) gcc doesn't conform to some standard,
(c) c99 is different on this point than ANSI C (I guess that's c89 but
am not really sure.

Cheers,

S
 
E

Eric Sosman

Correct. The loop body is entered and exited each time the loop
repeats, so any variables that are declared within the loop body come
into scope and go out of scope on each iteration.

More precisely, the scope of an identifier is static:
it is a consequence of the lexical structure of the program,
not of its behavior at run-time. The "scope" of an identifier
is the region of the code in which that identifier is associated
with a particular purpose -- its binding, if you will.

If an identifier refers to a data object (as opposed to,
say, an `enum' constant), we can also ask about the "storage
duration" of the object -- its "lifetime." When execution
enters a {}-enclosed block, the data objects for any `auto'
or `register' variables belonging to that block come into
existence. They remain in existence until execution leaves
the block, at which point they return to the limbo whence
they came. If the block is re-entered, new data objects are
created for the new execution; they may (or may not) occupy
the same memory locations as the data objects from the first
execution, but in every essential they are brand-new objects,
independent of those that existed the first time around.

An easy way to observe this independence is through
recursion:

void f(unsigned int depth) {
auto /* for emphasis */ int variable;
printf ("Entering level %u: `variable' is at %p\n",
depth, (void*) &variable);
if (depth > 0)
f(depth - 1);
printf ("Leaving level %u: `variable' is at %p\n",
depth, (void*) &variable);
}

Call this as `f(3)', say, and you'll easily see that the
identifier `variable' refers to different objects at different
executions of the {}-enclosed block that is the function body.

Back to the original question about a `for' loop (`while'
and `do' loops behave exactly the same way) of the form

for (i = 0; i < 10; ++i ) {
int j;
...
}

The scope of the identifier `j' extends from its declaration
to the ending } -- this is the static code region during which
the binding of `j' to a particular data object exists. The
ten executions of the loop body create and destroy ten data
objects for `j' to refer to. It will often turn out that the
ten objects will all occupy the same memory location or register,
but they are different objects nonetheless because their lifetimes
do not overlap. In particular, it is *not* guaranteed that a
value assigned to `j' in one iteration will still be intact
when the next iteration begins.
 
C

Chris Torek

E. Robert Tisdale said:
cat main.c
#include<stdio.h>

int main(int argc, char* argv[]) {
const int n = 10;
for (int i = 0; i < n; ++i) {
fprintf(stdout, "%d = i\n", i);
int i = 0;
fprintf(stdout, "%d = i\n", i);
}
return 0;
}

Very interesting example. While the "first i" and "second i" occur
within the same block, your example makes it appear there are is no
scoping conflict. Is this because
(a) scoping is largely dependent on declarations, and declarations
only extend downwards (here, the declarations *don't* occur in the
same block, and the "second i" is declared after the first one is used
but not declared), or
(b) gcc doesn't conform to some standard,
(c) c99 is different on this point than ANSI C (I guess that's c89 but
am not really sure.

It is indeed an interesting example, and the answers to your three
questions are "yes (depending on what you mean by `largely')",
"no", and "no" :) (Not that gcc, even with -std=c99, quite conforms
to C99 -- but any nonconformance is not the reason the example
works. Further, "ANSI C" can be either or both of both C89 and
C99, depending on who is saying the words.)

According to the draft C99 standard I have handy in text form:

Except for the behavior of a continue statement in the
loop body, the statement

for ( clause-1 ; expr-2 ; expr-3 ) statement

and the sequence of statements

{
clause-1 ;
while ( expr-2 ) {
statement
expr-3 ;
}
}

are equivalent ....

We are also told that:

Two identifiers have the same scope if and only if their
scopes terminate at the same point.

Applying the prescribed transformation to the loop:

for (int i = 0; i < n; i++)
{ ... }

produces:

{
int i = 0;
while (i < n) {
{ ... }
i++;
}
}

Note that we now have not one, nor even two, but THREE (!) sets of
scope-affecting "{}"s. The "for" loop's declaration of identifier
i occurs inside the outermost {}s and terminates at the final }.
The second declaration of "i" -- in the "..." part -- occurs inside
the innermost {}s and terminates at the first }. These two "i"s
thus have different scopes, and can (and do) name different objects.

The C99 standard's expansion, in which a "for" loop has at least
two implied "{"s, is the direct answer to your original question.
 
M

Micah Cowan

E. Robert Tisdale said:
Stephen said:
What are the scoping rules for a for loop?

Example:
cat main.c
#include<stdio.h>

int main(int argc, char* argv[]) {
const int n = 10;
for (int i = 0; i < n; ++i) {
fprintf(stdout, "%d = i\n", i);
int i = 0;
fprintf(stdout, "%d = i\n", i);
}
return 0;
}

Thanks for your reply.

Very interesting example. While the "first i" and "second i" occur
within the same block, your example makes it appear there are is no
scoping conflict. Is this because
(a) scoping is largely dependent on declarations, and declarations
only extend downwards (here, the declarations *don't* occur in the
same block, and the "second i" is declared after the first one is used
but not declared), or
(b) gcc doesn't conform to some standard,
(c) c99 is different on this point than ANSI C (I guess that's c89 but
am not really sure.

C99 *is* ANSI C--or more accurately, it is ISO C. It is the newer
standard.

C99 is *very* different on this point than C89, as in C89 both of the
above declarations of i are illegal. In the first case, declarations
are not allowed in the first clause of a for statement; in the second,
declarations must precede all statements at the same block level.

The answer to your question is: none of the above. The reason is that
the first i and second i *don't* occur within the same
block. Declarations occuring in the first clause of a for statement
exist in a block beginning right there, and extending to the end of
the statement (the end of the for loop). The second declaration,
however, exists within a separate, inner block which begins with the
opening { brace, and ends, along with the outer block, with the }
brace.

-Micah
 
C

CBFalconer

Micah said:
.... snip ...
cat main.c
#include<stdio.h>

int main(int argc, char* argv[]) {
const int n = 10;
for (int i = 0; i < n; ++i) {
fprintf(stdout, "%d = i\n", i);
int i = 0;
fprintf(stdout, "%d = i\n", i);
}
return 0;
}
.... snip ...

The answer to your question is: none of the above. The reason
is that the first i and second i *don't* occur within the same
block. Declarations occuring in the first clause of a for
statement exist in a block beginning right there, and extending
to the end of the statement (the end of the for loop). The
second declaration, however, exists within a separate, inner
block which begins with the opening { brace, and ends, along
with the outer block, with the } brace.

Which is just the sort of thing that makes parsing C such a bear,
and impedes really serious complete description in BNF terms.
That means that a for statement needs to be described by something
like:

for-statement = 'for' <for-block>
for-block = '(' <for-header> ')' <statement-block>

to provide syntactical hooks for permissible declarations. It
gets worse.
 
L

Len Budney

I assume that's consistent with the example another poster (Tisdale)
gave?:

for (int i = 0; i < n; ++i) {
fprintf(stdout, "%d = i\n", i);
int i = 0;
fprintf(stdout, "%d = i\n", i);
}

His example treats the two instances of i differently (as if the
second were "j", not "i"). I'm assuming this is because "It's value
is undefined from the beginning of the block until initialised or
assigned a value."

No. At least, poorly articulated. The scope of the first 'i' is the
for statement and the dependent block--i.e., larger than the block.
The scope of the second 'i' is from its declaration to the end of the
block. The second 'i' hides the first 'i' within its scope--but at the
bottom of the block, the second 'i' passes out of scope, the loop
counter is exposed, and the loop condition is tested correctly.

Also note that the second 'i' behaves differently than if it were 'j',
in that the loop variable cannot be accessed from within the scope of
the second 'i'.

Some compilers, that don't implement C99 correctly (including VC++),
would treat the loop variable as having scope equal to the block
containing the for statement, and would disallow a loop later in the
block using 'i' as its counter.
By "discretion", "no correct answer," "practical reason," "don't
depend on it," I assume you mean there's nothing in the standards to
make it go the way I think it goes.

Indubitably. Once a variable passes out of scope, its location may
contain the last value, another variable's value--in fact, anything.
With optimization turned on, it's very likely to contain something
else.

--Len.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top