会使用goto泄漏变量吗?


94

goto在不调用析构函数和事物的情况下跨代码段跳转是真的吗?

例如

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

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

不会x泄漏吗?


相关:stackoverflow.com/questions/1258201/…(但我想从头开始,干净利落!)
Lightness Races in Orbit

15
什么"Won't x be leaked"意思 的类型x是内置数据类型。您为什么不选择一个更好的例子?
Nawaz

2
@Nawaz:这个例子很完美。几乎每次我与某人谈论时goto,他们都认为即使自动存储持续时间变量也以某种方式“泄漏”。你和我所不知道的完全不是重点。
Lightness Races in Orbit

1
@David:我同意,当变量具有非平凡的析构函数时,此问题更有意义……我查看了Tomalak的答案并找到了这样的例子。此外,尽管int不能泄漏,但它还是可以泄漏的。例如:void f(void) { new int(5); }泄漏int
Ben Voigt

为什么不将问题更改为“在给定的示例中,代码执行路径将从f()转换为main()而不清除堆栈和其他从函数返回的功能吗?是否需要使用析构函数会不会很重要?叫吗?在C中是一样的吗?” 既可以保持问题的意图,又可以避免可能的误解?
Jack V.

Answers:


210

警告:此答案与C ++有关;在C中,规则完全不同。


不会x泄漏吗?

不,绝对不是。

这是一个goto低层构造的神话,它允许您覆盖C ++的内置作用域机制。(如果有的话,很longjmp可能会发生这种情况。)

考虑以下机制,这些机制可防止您对标签(包括case标签)进行“不良操作” 。


1.标签范围

您不能跳过以下功能:

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

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

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

[n3290: 6.1/1]:[..]标签的范围是显示标签的功能。[..]


2.对象初始化

您不能跳过对象初始化:

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

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

如果你跳跨对象初始化,则该对象的以前的“实例”被破坏

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]:[..]循环外,块外或具有自动存储持续时间的初始化变量的转移涉及具有自动存储持续时间的对象的销毁,这些对象在从(从)转移到(从)转移的时间点范围内。[..]

您不能跳入对象的范围,即使未明确初始化它:

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

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

... ... 某些对象除外,该语言可以处理,因为它们不需要“复杂”的构造:

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

// OK

[n3290: 6.7/3]:可以转移到块中,但不能以初始化绕过声明的方式进行。程序将从具有自动存储持续时间的变量不在范围内的点跳转到其处于范围内的点的格式错误,除非该变量具有标量类型,具有平凡的默认构造函数和平凡的析构函数的类类型,这些类型之一的cv限定版本,或者上述类型之一的数组,并且在没有初始化程序的情况下进行了声明。[..]


3.遵守其他对象的范围

同样,当您超出范围,具有自动存储期限的对象不会 “泄漏”goto

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

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

lol:
   return 0;
}

// *T~T

[n3290: 6.6/2]:在退出合并范围时(无论如何完成),在该合并范围中构造的具有自动存储持续时间(3.7.3)的对象将按照与其构造相反的顺序进行销毁。[..]


结论

以上机制确保goto不会破坏您的语言。

当然,这并非自动意味着您“应该”使用goto任何给定的问题,但这确实意味着它不像普通神话所引起的人们那样相信“邪恶”。


8
您可能会注意到C并不能阻止所有这些危险的事情发生。
丹尼尔(Daniel)

13
@Daniel:问题和答案是关于C ++的,但很公平。也许我们可以有另一个常见问题解答,以消除C和C ++相同的神话;)
轨道竞赛

3
@Tomalak:我不认为我们在这里有不同意见。SO上给出的许多答案都明确记录在某处。我只是做它可能是诱人的C程序员看到这个答案,并认为,如果它工作在C ++中,它应该同样在C.工作点
丹尼尔

2
您可能还想补充一点,对于案例标签,所有这些跳过初始化的事情都是相同的。
PlasmaHH 2011年

12
哇,我只是以为C ++的语义已经无法使用goto了,但是它们出奇的理智!好答案。
Joseph Garvin 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.