99

Is it true that goto jumps across bits of code without calling destructors and things?

e.g.

void f() {
   int x = 0;
   goto lol;
}

int main() {
   f();
lol:
   return 0;
}

Won't x be leaked?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Related: http://stackoverflow.com/questions/1258201/will-using-goto-cause-memory-leaks (but I wanted to do it from scratch, cleanly!) – Lightness Races in Orbit Sep 07 '11 at 13:49
  • 15
    What does `"Won't x be leaked"` mean? The type of `x` is a built-in data type. Why don't you choose a better example? – Nawaz Sep 07 '11 at 14:05
  • 2
    @Nawaz: The example is perfect the way it is. Almost every time I talk to somebody about `goto`, they think that even automatic storage duration variables are somehow "leaked". That you and I know otherwise is completely besides the point. – Lightness Races in Orbit Sep 07 '11 at 14:05
  • 1
    @David: I agree that this question makes a lot more sense when the variable has a non-trivial destructor... and I looked in Tomalak's answer and did find such an example. Furthermore, while an `int` cannot leak, it can *be leaked*. For example: `void f(void) { new int(5); }` leaks an `int`. – Ben Voigt Sep 07 '11 at 14:42
  • Why not change the question to something like "In the example given, will the code executation path transfer from f() to main() without clearing the stack and other return-from-function functionality? Would it matter if a destructor needed to be called? Is is the same in C?" Would that both maintain the intent of the question, whilst avoid the possible misconceptions? – Jack V. Sep 14 '11 at 13:39
  • @Jack: Yep, I agree with that (though the language there may have a high entry requirement of knowledge) – Lightness Races in Orbit Sep 14 '11 at 13:49

1 Answers1

216

Warning: This answer pertains to C++ only; the rules are quite different in C.


Won't x be leaked?

No, absolutely not.

It is a myth that goto is some low-level construct that allows you to override C++'s built-in scoping mechanisms. (If anything, it's longjmp that may be prone to this.)

Consider the following mechanics that prevent you from doing "bad things" with labels (which includes case labels).


1. Label scope

You can't jump across functions:

void f() {
   int x = 0;
   goto lol;
}

int main() {
   f();
lol:
   return 0;
}

// error: label 'lol' used but not defined

[n3290: 6.1/1]: [..] The scope of a label is the function in which it appears. [..]


2. Object initialisation

You can't jump across object initialisation:

int main() {
   goto lol;
   int x = 0;
lol:
   return 0;
}

// error: jump to label ‘lol’
// error:   from here
// error:   crosses initialization of ‘int x’

If you jump back across object initialisation, then the object's previous "instance" is destroyed:

struct T {
   T() { cout << "*T"; }
  ~T() { cout << "~T"; }
};

int main() {
   int x = 0;

  lol:
   T t;
   if (x++ < 5)
     goto lol;
}

// Output: *T~T*T~T*T~T*T~T*T~T*T~T

[n3290: 6.6/2]: [..] Transfer out of a loop, out of a block, or back past an initialized variable with automatic storage duration involves the destruction of objects with automatic storage duration that are in scope at the point transferred from but not at the point transferred to. [..]

You can't jump into the scope of an object, even if it's not explicitly initialised:

int main() {
   goto lol;
   {
      std::string x;
lol:
      x = "";
   }
}

// error: jump to label ‘lol’
// error:   from here
// error:   crosses initialization of ‘std::string x’

... except for certain kinds of object, which the language can handle regardless because they do not require "complex" construction:

int main() {
   goto lol;
   {
      int x;
lol:
      x = 0;
   }
}

// OK

[n3290: 6.7/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. [..]


3. Jumping abides by scope of other objects

Likewise, objects with automatic storage duration are not "leaked" when you goto out of their scope:

struct T {
   T() { cout << "*T"; }
  ~T() { cout << "~T"; }
};

int main() {
   {
      T t;
      goto lol;
   }

lol:
   return 0;
}

// *T~T

[n3290: 6.6/2]: On exit from a scope (however accomplished), objects with automatic storage duration (3.7.3) that have been constructed in that scope are destroyed in the reverse order of their construction. [..]


Conclusion

The above mechanisms ensure that goto doesn't let you break the language.

Of course, this doesn't automatically mean that you "should" use goto for any given problem, but it does mean that it is not nearly as "evil" as the common myth leads people to believe.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055