是不是真的goto
可以跳过代码的一部分而不调用析构函数和其他东西?
例如:
void f() {
int x = 0;
goto lol;
}
int main() {
f();
lol:
return 0;
}
不会泄漏
x
吗?是不是真的goto
可以跳过代码的一部分而不调用析构函数和其他东西?
例如:
void f() {
int x = 0;
goto lol;
}
int main() {
f();
lol:
return 0;
}
x
吗?警告:本答案仅适用于C++;在C中规则完全不同。
x
不会泄漏吗?
不会,绝对不会。
goto
是一种低级构造的神话,它让您可以覆盖C++的内置作用域机制。(如果有什么问题,那可能是 longjmp
的问题。)
请考虑以下机制,以防止您在标签上做出“坏事”(包括case
标签)。
您不能跨函数进行跳转:
void f() {
int x = 0;
goto lol;
}
int main() {
f();
lol:
return 0;
}
// error: label 'lol' used but not defined
[n3290: 6.1/1]:
[..] 标签的作用域是它所在的函数。[..]
你不能跨越对象初始化进行跳转:
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]:
可以将控制流转移到块内,但不能绕过带初始化的声明。一个程序从一个自动存储期变量不在其作用域的点跳到其作用域所在的点是不合法的,除非该变量具有标量类型、具有平凡的默认构造函数和平凡的析构函数的类类型、这些类型中的一个的const或volatile限定版本或一个不带初始化符号的前述类型的数组。[..]
同样地,当你使用 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
,但这意味着它并没有常见神话所认为的那么 "邪恶"。
x
是一种内置的数据类型。为什么不选择一个更好的例子呢? - Nawazint
不能泄漏,但它可以被泄漏。例如:void f(void) { new int(5); }
会泄漏一个int
。 - Ben Voigt