How can a variable be used when its definition is

2020-02-03 09:32发布

问题:

In my mind, always, definition means storage allocation.

In the following code, int i allocates a 4-byte (typically) storage on program stack and bind it to i, and i = 3 assigns 3 to that storage. But because of goto, definition is bypassed which means there is no storage allocated for i.

I heard that local variables are allocated either at the entry of the function (f() in this case) where they reside, or at the point of definition.

But either way, how can i be used while it hasn't been defined yet (no storage at all)? Where does the value three assigned to when executing i = 3?

void f()
{
    goto label;
    int i;

label:
    i = 3;
    cout << i << endl; //prints 3 successfully
}

回答1:

Long story short; goto will result is a runtime jump, variable definition/declaration will result in storage allocation, compile time.

The compiler will see and decide on how much storage to allocate for an int, it will also make so that this allocated storage will be set to 3 when "hitting" i = 3;.

That memory location will be there even if there is a goto at the start of your function, before the declaration/definition, just as in your example.


Very silly simile

If I place a log on the ground and my friend runs (with his eyes closed) and jumps over it, the log will still be there - even if he hasn't seen or felt it.

It's realistic to say that he could turn around (at a later time) and set it on fire, if he wanted to. His jump doesn't make the log magically disappear.



回答2:

Your code is fine. The variable lives wherever it would live had the goto not been there.

Note that there are situations where you can't jump over a declaration:

C++11 6.7 Declaration statement [stmt.dcl]

3 It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer (8.5). [ Example:

void f()
{
    // ...
    goto lx;    // ill-formed: jump into scope of `a'
    // ...
ly:
    X a = 1;
    // ...
lx:
    goto ly;    // ok, jump implies destructor
                // call for `a' followed by construction
                // again immediately following label ly
}

—end example ]



回答3:

Definitions are not executable code. They are just instructions to the compiler, letting it know the size and the type of the variable. In this sense, the definition is not bypassed by the goto statement.

If you use a class with a constructor instead of an int, the call of the constructor would be bypassed by the goto, but the storage would be allocated anyway. The class instance would remain uninitialized, however, so using it before its definition/initialization line gets the control is an error.



回答4:

In my mind, always, definition means storage allocation.

This is not correct. The storage for the variable is reserved by the compiler when it creates the stack-layout for the function. The goto just bypasses the initialization. Since you assign a value before printing, everything is fine.



回答5:

The control of flow has nothing to do with variable's storage which is reserved at compile time by the compiler.

The goto statement only effects the dynamic initialization of the object. For built-in types and POD types, it doesn't matter, for they can remain uninitialized. However, for non-POD types, this would result in compilation error. For example see this

struct A{ A(){} };  //it is a non-POD type

void f()
{
    goto label;

    A a;     //error - you cannot skip this!

label:
    return;
}

Error:

prog.cpp: In function ‘void f()’:
prog.cpp:8: error: jump to label ‘label’
prog.cpp:5: error:   from here
prog.cpp:6: error:   crosses initialization of ‘A a’

See here : http://ideone.com/p6kau

In this example A is a non-POD type because it has user-defined constructor, which means the object needs to be dynamically initialized, but since the goto statement attempts to skip this, the compiler generates error, as it should.

Please note that objects of only built-in types and POD types can remain uninitialized.



回答6:

To make it short, variable declaration is lexical, i.e. pertaining to the lexical {}-enclosed blocks. The binding is valid from the line it is declared to the end of the block. It is unaffected by flow control (goto).

Variable assignment of locol (stack) variables, on the other hand, is a runtime operation performed when the control flow gets there. So goto has an influence on that.

Things get a bit more tricky when object construction becomes involved, but that's not your case here.



回答7:

The position of the declaration of i is irrelevant to the compiler. You can prove this to yourself by compiling your code with int i before the goto and then after and comparing the generated assembly:

g++ -S test_with_i_before_goto.cpp -o test1.asm
g++ -S test_with_i_after_goto.cpp -o test2.asm
diff -u test1.asm test2.asm

The only difference in this case is the source file name (.file) reference.



回答8:

The definition of a variable DOES NOT allocate memory for the variable. It does tell the compiler to prepare appropriate memory space to store the variable though, but the memory is not allocated when control passed the definition.

What really matters here is initialization.