17

Consider this code:

void foo()
{
    goto bar;
    int x = 0;
    bar: ;
}

GCC and Clang reject it, because the jump to bar: bypasses variable initialization. MSVC doesn't complain at all (except using x after bar: causes a warning).

We can do a similar thing with a switch:

void foo()
{
    switch (0)
    {
        int x = 0;
        case 0: ;
    }
}

Now all three compilers emit errors.

Are those snippets ill-formed? Or do they cause UB?

I used to think that both were ill-formed, but I can't find the revelant parts of the standard. [stmt.goto] doesn't say anything about this, and neither does [stmt.select].

curiousguy
  • 8,038
  • 2
  • 40
  • 58
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 1
    Issue would be more trivial if you use `x` after the jump. – Jarod42 Jan 27 '20 at 12:29
  • 1
    not the standard, but here one can find some information on it: https://en.cppreference.com/w/cpp/language/goto in particular: "If transfer of control enters the scope of any automatic variables (e.g. by jumping forward over a declaration statement), the program is ill-formed (cannot be compiled), unless ..." – 463035818_is_not_an_ai Jan 27 '20 at 12:30
  • Add the `/permissive-` flag to MSVC and it will complain as well. I don't know though whether MSVC's behavior without that flag is well-defined (I would assume so, otherwise why would they allow it?). – walnut Jan 27 '20 at 12:47
  • @walnut *"otherwise why would they allow it"* Possibly for backward compatibity, or because they don't care about the standard too much. All major compilers don't conform to the standard under default settings. – HolyBlackCat Jan 27 '20 at 13:04
  • https://stackoverflow.com/a/7334968/4386278 – Asteroids With Wings Jan 27 '20 at 13:08

2 Answers2

20

It's ill-formed when the initialization is non-vacuous.

[stmt.dcl]

3 It is possible to transfer into a block, but not in a way that bypasses declarations with initialization (including ones in conditions and init-statements). 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 vacuous initialization ([basic.life]). In such a case, the variables with vacuous initialization are constructed in the order of their declaration.

The initializer makes the initialization non-vacuous. To contrast, this

void foo()
{
    goto bar;
    int x; // no initializer
    bar: ;
}

would be well-formed. Though the usual caveats about using x with an indeterminate value would apply.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
4

From goto statement:

If transfer of control enters the scope of any automatic variables (e.g. by jumping forward over a declaration statement), the program is ill-formed (cannot be compiled), unless all variables whose scope is entered have

  1. scalar types declared without initializers
  2. class types with trivial default constructors and trivial destructors declared without initializers
  3. cv-qualified versions of one of the above
  4. arrays of one of the above
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
TruthSeeker
  • 1,539
  • 11
  • 24