以下代码在 g++ 和 Visual C++ 下都可以通过编译。为什么是合法的?它看起来不合理,并且可能会导致隐藏的错误。
int main() {
int i = i;
}
以下代码在 g++ 和 Visual C++ 下都可以通过编译。为什么是合法的?它看起来不合理,并且可能会导致隐藏的错误。
int main() {
int i = i;
}
编辑:它在语法上是合法的,但如果你使用x
.
这是不合法的,因为您正在为一个未初始化的变量分配另一个(好吧,相同的)未初始化的变量。仅仅因为它编译并不意味着它是合法的。这是有效的 C++ 语法,是的,但不合法。
赋值运算符的右侧必须在赋值时进行全面评估。在这种情况下,就是i
,它没有被初始化。
感谢 Steve Jessop,他挖出了这句话:
4.1/1,左值到右值的转换
[...] 如果对象未初始化,则需要此转换的程序具有未定义的行为。
语法允许它的原因是,在某些奇怪的情况下,您可能想在自己的初始化程序中通过指针或引用来使用变量:
struct ThingManager {
void *thing;
ThingManager(void *thing) : thing(thing) {}
void Speak() {
if (thing == (void*)this) {
std::cout << "I'm managing myself\n";
} else {
std::cout << "I'm managing " << thing << "\n";
}
}
};
ThingManager self_manager(&self_manager);
ThingManager other_manager(&self_manager);
因此,C++ 允许您在其自己的初始化表达式中引用一个对象(其名称在范围内)。然后像以往一样在 C++ 中,确保您实际上不使用未初始化的值是您的问题(您的示例int i = i;
确实使用了未初始化的值)。
您的编译器可能有助于识别未初始化值的使用,但标准并不要求这样做。
您可以使用(与 结合使用)g++
警告您有关此用例的信息,如果您将警告视为错误,它应该满足您的需求。-Winit-self
-Wuninitialized
这种使用复制构造函数进行自初始化的技术有时用于禁止执行全局对象的默认构造函数/初始化程序。如果全局对象的默认构造函数只是为了0
初始化对象,但该对象在构造函数被执行之前被使用,这可能是必要的。作为对 C 的回归,全局变量0
在程序启动时被初始化,然后 C++ 运行时开始执行全局构造函数。对于那些将执行的已定义构造函数仅用于0
输出对象的狭窄情况,自初始化不会造成任何伤害。
在一般情况下,复制构造函数自初始化是不好的做法,因为它通常会导致与使用未初始化变量相同的问题(即未定义的行为)。在 OP 问题的特定示例中,i
是本地的main
,因此未初始化。读取未初始化变量的结果始终是未定义的行为。
您可以使用任何先前声明的变量作为另一个变量的初始化程序。
在这种情况下,编译器一解析int i
就将其添加到符号表中,因此当它看到= i
初始化程序时,可以从前面的声明中解析符号。
这不是一个错误,因为编译器可以理解它,因为它可以生成明确地执行源代码指定的代码,即使它在语义上是可疑的。C 和 C++ 的哲学是编译任何可以在语法上编译的东西。语义错误通常只发出警告,并且只有在启用此类警告时才发出。