以下代码可以在g ++和Visual C ++下通过编译。为什么合法?它看起来不合理,可能会导致隐藏的错误。
int main() {
int i = i;
}
答案 0 :(得分:44)
编辑: 语法合法,但如果您使用x
,则会导致未定义的行为。
不合法,因为您将未初始化的变量与另一个(好的,相同的)未初始化变量分配。仅仅因为它编译并不意味着它是合法的。它是有效的C ++语法,是的,但不合法。
在分配时,必须对赋值运算符的右侧进行全面评估。在这种情况下,那是i
,它没有被初始化。
对史蒂夫杰索普的信任,他挖出了这句话:
4.1 / 1,左值到右值转换
[...]如果对象未初始化,则需要一个程序 转换有未定义的行为。
答案 1 :(得分:14)
语法允许的原因是,在某些奇怪的情况下,您可能会想到在自己的初始值设定项中使用指针或引用变量:
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;
确实使用了未初始化的值)。
您的编译器可能有助于识别未初始化值的使用,但标准并不要求它。
答案 2 :(得分:7)
您可以g++
使用-Winit-self
(与-Wuninitialized
一起)警告0
这个用例,如果您将警告视为错误,则应该满足您的需求。
这种使用复制构造函数进行自初始化的技术有时用于抑制全局对象的默认构造函数/初始化程序的执行。如果全局对象的默认构造函数只是0
初始化对象,但在构造函数执行之前使用了对象,则可能需要这样做。作为C的回归,在程序启动时初始化全局变量0
,然后C ++运行时开始执行全局构造函数。对于那些已执行的已定义构造函数仅为i
对象的狭窄情况,自我初始化不会造成任何伤害。
在一般情况下,复制构造函数自我初始化是不好的做法,因为它通常会导致使用未初始化的变量导致的相同类型的问题(即,未定义的行为)。在OP问题的特定示例中,main
是{{1}}的本地,因此未初始化。读取未初始化变量的结果始终是未定义的行为。
答案 3 :(得分:2)
您可以使用任何先前声明的变量作为另一个变量的初始化。
在这种情况下,只要编译器解析int i
,它就会将其添加到符号表中,因此当它看到= i
初始值时,可以从前面的声明中解析符号。
这不是错误,因为编译器可以理解它,因为它可以生成明确完成源代码指定的代码,如果它在语义上是可疑的,则为7。 C和C ++的哲学是编译任何可能在语法上编译的东西。语义错误通常只发出警告,只有在启用此类警告时才会发出警告。