14

Is it allowed to jump to a label that's inside an inner scope or a sibling scope? If so, is it allowed to use variables declared in that scope?

Consider this code:

int cond(void);
void use(int);

void foo() 
{
    {
        int y = 2;
        label:
        use(y);
    }

    {
        int z = 3;
        use(z);

        /* jump to sibling scope: */ if(cond()) goto label;
    }

    /* jump to inner scope: */ if(cond()) goto label;
}

Are these gotos legal?

If so, is y guaranteed to exist when I jump to label and to hold the last value assigned to it (2)?

Or is the compiler allowed to assume y won't be used after it goes out of scope, which means a single memory location may be used for both y and z?

If this code's behavior is undefined, how can I get GCC to emit a warning about it?

2501
  • 25,460
  • 4
  • 47
  • 87
Spike
  • 725
  • 5
  • 12
  • Why are you even using `goto`? – Ed Heal May 22 '16 at 07:46
  • 6
    @EdHeal I'm just asking to understand C. I'm not actually using this code. – Spike May 22 '16 at 07:50
  • 8
    `goto`'s are invaluable for breaking out of (or in to) nested loops that would otherwise lend themselves to longer or recursive solutions given the limitations on `break`. The little old `goto` is a workhorse in this case. The C-library make frequent use of `goto`. Look at the `qsort` source code, `strlen` if I recall, etc.. – David C. Rankin May 22 '16 at 08:34
  • 4
    @DavidC.Rankin, you forgot the simplification `goto` lends to cleanup code, as demonstrated repeatedly by the Linux kernel. – StoryTeller - Unslander Monica May 22 '16 at 09:06

2 Answers2

10

The jumps are legal (in C, in C++ they aren't).

is y guaranteed to exist when I jump to label

Yes.

and to hold the last value assigned to it (2)?

No.

From the C11 Standard (draft) 6.2.4/6:

For such an object [without the storage-class specifier static] that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way. [...] The initial value of the object is indeterminate. If an initialization is specified for the object, it is performed each time the declaration [...] is reached in the execution of the block; otherwise, the value becomes indeterminate each time the declaration is reached.

From the above one would conclude the for the 2nd and 3rd time use(y) gets called the value of y ins "indeterminate[d]", as the initialisation of y is not "reached".

alk
  • 69,737
  • 10
  • 105
  • 255
10

From the C99 standard (emphasis mine):

6.2.4 Storage durations of objects

[6] For such an object that does have a variable length array type, its lifetime extends from the declaration of the object until execution of the program leaves the scope of the declaration. ... If the scope is entered recursively, a new instance of the object is created each time. The initial value of the object is indeterminate.

6.8.6.1 The goto statement

[1] The identifier in a goto statement shall name a label located somewhere in the enclosing function. A goto statement shall not jump from outside the scope of an identifier having a variably modified type to inside the scope of that identifier.

[4] ... A goto statement is not allowed to jump past any declarations of objects with variably modified types.

Conclusion

  1. y is not a variably modified type, so, according to the standard, the jumps are legal.

  2. y is guaranteed to exist, however, the jumps skip the initialization (y = 2), so the value of y is indeterminate.

  3. You can use -Wjump-misses-init to get GCC to emit a warning like the following:

    warning: jump skips variable initialization [-Wjump-misses-init]


In C++, the jumps are not legal, C++ does not allow to skip the initialization of y.

sergej
  • 17,147
  • 6
  • 52
  • 89
  • For future readers: a "variably modified type" would include for example a VLA like `char buf[n]`, or a pointer whose type includes a variable size, like `char (*vla_ptr)[n]` (i.e. a [2D array pointer](https://stackoverflow.com/questions/28524896/casting-pointer-to-memory-buffer-to-pointer-to-vla) with variable bounds). – Peter Cordes Jun 02 '21 at 02:08