出于好奇,我尝试了这段代码,这是因为面试问题[*]
int main(int argc, char *argv[])
{
int a = 1234;
printf("Outer: %d\n", a);
{
int a(a);
printf("Inner: %d\n", a);
}
}
在Linux(g ++ 4.6.3和clang ++ 3.0)上编译时,它输出:
Outer: 1234
Inner: -1217375632
然而,在Windows(VS2010)上,它会打印:
Outer: 1234
Inner: 1234
基本原理是,在第二个'a'变量的拷贝构造函数完成之前,第一个'a'变量仍然可以访问。但是我不确定这是标准行为,还是仅仅是(另一个)微软的怪癖。
有什么想法吗?
[*]实际问题是:
如何在不使用临时变量或全局变量的情况下使用包含范围中具有相同名称的变量的值初始化范围内的变量?
{
// Not at global scope here
int a = 1234;
{
int a;
// how do you set this a to the value of the containing scope a ?
}
}
答案 0 :(得分:10)
如何在不使用临时变量或全局变量的情况下使用包含范围中具有相同名称的变量的值初始化范围内的变量?
除非可以明确命名外部范围,否则无法执行此操作。您可以显式命名全局范围,命名空间范围和类范围,但不能显式命名函数或块语句范围。
C ++ 11 [basic.scope.pdecl 3.3.2 p1状态:
名称的声明紧接在其完整声明者(第8条)之后和初始化程序之前(如果有),除非如下所述。 [示例:
int x = 12; { int x = x; }
这里第二个x用自己的(不确定的)值初始化。 - 示例]
MSVC正确实现了此示例,但是当初始化程序使用括号而不是赋值语法时,它无法正确实现此示例。有关此问题的a bug已在microsoft connect上提交。
这是由于此错误导致VS中的行为不正确的示例程序。
#include <iostream>
int foo(char) { return 0; }
int foo(int) { return 1; }
int main()
{
char x = 'a';
{
int x = foo(static_cast<decltype(x)>(0));
std::cout << "'=' initialization has correct behavior? " << (x?"Yes":"No") << ".\n";
}
{
int x(foo(static_cast<decltype(x)>(0)));
std::cout << "'()' initialization has correct behavior? " << (x?"Yes":"No") << ".\n";
}
}
C ++包含以下注释。
[注意:涉及不确定值的操作可能会导致未定义的行为。 - 注意]
但是,此注释表明操作可能导致未定义的行为,而不是它们必须执行。上面链接的错误报告包括Microsoft的确认,这是一个错误,而不是程序触发未定义的行为。
编辑:现在我已经更改了示例,以便具有不确定值的对象仅在未评估的上下文中“使用”,我相信这绝对排除了未定义行为的可能性在任何平台上,同时仍然在Visual Studio中演示错误。
答案 1 :(得分:6)
如何在不使用临时变量或全局变量的情况下使用包含范围中具有相同名称的变量的值初始化范围内的变量?
如果你想获得有关措辞的技术,那就很容易了。 “临时”在C ++中具有特定含义(见第12.2节);您创建的任何命名变量都不是临时的。因此,您只需创建一个使用正确值初始化的局部变量(不临时变量):
int a = 1234;
{
int b = a;
int a = b;
}
更可靠的可能性是在外部范围内使用对变量的引用:
int a = 1234;
{
int &ref_a = a;
int a = ref_a;
}
这根本不会创建额外的变量 - 它只是在外部范围创建变量的别名。由于别名具有不同的名称,因此我们保留对外部作用域的变量的访问权限,而无需定义变量(临时或其他)来执行此操作。许多引用在内部实现为指针,但在这种情况下(至少打开现代编译器和优化)我希望它不会 - 别名真的只会被视为一个引用变量的不同名称在外部范围(VC ++的快速测试表明它以这种方式工作 - 生成的汇编语言根本不使用ref_a
。
沿着同样的路线的另一种可能性是:
const int a = 10;
{
enum { a_val = a };
int a = a_val;
}
这有点类似于引用,除了在这种情况下甚至没有关于a_val
是否可以被称为变量的争论的空间 - 它绝对是不是变量。问题是枚举只能用常量表达式初始化,因此我们必须将外部变量定义为const
才能使其工作。
我怀疑这些是面试官的真正意图,但所有人都回答了所说的问题。第一个(诚然)是关于术语定义的纯技术性。第二个可能仍然对一些论点持开放态度(许多人认为引用是变量)。虽然它限制了范围,但对于第三个问题没有任何问题或争论的余地。
答案 2 :(得分:2)
您正在做的事情,用自己初始化变量,是未定义的行为。你的所有测试用例都是正确的,这不是一个怪癖。实现还可以将a
初始化为123456789
,它仍然是标准的。
更新:对此答案的评论指出,使用自身初始化变量不是未定义的行为,但尝试读取此类变量是。
答案 3 :(得分:1)
如何在不使用临时变量或全局变量的情况下使用包含范围中具有相同名称的变量的值初始化范围内的变量?
你做不到。一旦声明了相同的名称,外部名称就无法访问范围的其余部分。您需要外部变量的副本或别名,这意味着您需要一个临时变量。
我很惊讶,即使警告级别上升,VC ++也没有抱怨这条线路:
int a(a);
Visual C ++有时会警告您隐藏变量(可能仅适用于派生类的成员)。在初始化之前告诉你正在使用一个值通常也很好,这就是这种情况。
查看生成的代码,它恰好将内部a初始化为外部a的相同值,因为这是在寄存器中留下的内容。
答案 4 :(得分:0)
我看过这个标准,它实际上是一个灰色区域,但这是我的2美分......
3.1声明和定义[basic.def]
声明将名称引入翻译单元或重新声明先前声明引入的名称。
- 醇>
声明是一种定义,除非...... [非相关案例如下]
3.3.1声明要点
名称的声明点紧跟在其完整的声明者之后和初始化者之前(如果有的话),除非下面有[自我分配示例]。
- 醇>
非本地名称在声明隐藏它的本地名称之前仍然可见。
现在,如果我们假设这是内部'a'(3.3.1 / 1)的声明点
int a (a);
^
然后外部'a'应该可见到那一点(3.3.1 / 2),其中内部'a'被定义。
问题在于,在这种情况下,根据3.1 / 2,声明是一个定义。这意味着应该创建内部'a'。在此之前,我无法从标准中理解外部“a”是否仍然可见。 VS2010假定它是,并且括号内的所有内容都指外部范围。但是,clang ++和g ++将该行视为自我赋值的一种情况,这会导致未定义的行为。
我不确定哪种方法是正确的,但我发现VS2010更加一致:在完全创建内部'a'之前,外部范围仍然可见。