7

出于好奇,我尝试了这段代码,来自一个面试问题[*]

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 ?
    }
}
4

5 回答 5

10

如何在不使用临时变量或全局变量的情况下使用包含范围内的同名变量的值来初始化范围内的变量?

除非可以显式命名外部范围,否则您不能这样做。您可以显式命名全局范围、命名空间范围和类范围,但不能显式命名函数或块语句范围。


C++11 [basic.scope.pdecl 3.3.2 p1 状态:

名称的声明点紧跟在它的完整声明符(第 8 条)之后和它的初始化器(如果有的话)之前,除非下面提到。[示例:

int x = 12;
{ int x = x; }

这里第二个 x 用它自己的(不确定的)值初始化。—结束示例]

MSVC 正确地实现了这个例子,但是当初始化器使用括号而不是赋值语法时它没有正确地实现它。在 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 的确认,即这是一个错误,而不是程序触发未定义的行为。

编辑:现在我已经更改了示例,以便具有不确定值的对象仅在未评估的上下文中“使用”,我相信这绝对排除了任何平台上未定义行为的可能性,同时仍然展示了错误视觉工作室。

于 2012-05-23T16:28:34.647 回答
7

如何在不使用临时变量或全局变量的情况下使用包含范围内的同名变量的值来初始化范围内的变量?

如果您想获得有关措辞的技术知识,那很容易。“临时”在 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才能使其工作。

我怀疑其中任何一个都是面试官真正想要的,但他们都回答了所陈述的问题。第一个是(诚然)关于术语定义的纯技术性。第二个可能仍然对某些论点持开放态度(许多人认为引用是变量)。虽然它限制了范围,但没有关于第三个问题或争论的余地。

于 2012-05-23T17:44:38.760 回答
2

你正在做的,用它自己初始化一个变量,是未定义的行为。你所有的测试用例都做对了,这不是一个怪癖。一个实现也可以初始化a123456789,它仍然是标准的。

更新:对此答案的评论指出,用自身初始化变量不是未定义的行为,但尝试读取此类变量是。

于 2012-05-23T16:14:19.987 回答
1

如何在不使用临时变量或全局变量的情况下使用包含范围内的同名变量的值来初始化范围内的变量?

你不能。一旦声明了相同的名称,外部名称就无法在其余范围内访问。您需要外部变量的副本或别名,这意味着您需要一个临时变量。

我很惊讶,即使警告级别提高了,VC++ 也没有在这一行抱怨:

int a(a);

Visual C++ 有时会警告您隐藏变量(可能只针对派生类的成员)。在初始化之前告诉你你正在使用一个值通常也很好,这里就是这种情况。

查看生成的代码,它恰好将内部 a 初始化为与外部 a 相同的值,因为那是寄存器中留下的内容。

于 2012-05-23T16:49:29.033 回答
0

我看了一下标准,它实际上是一个灰色区域,但这是我的 2 美分......

3.1 声明和定义 [basic.def]

  1. 声明将名称引入翻译单元或重新声明先前声明引入的名称。

  2. 声明是一个定义,除非... [以下非相关情况]

3.3.1 声明点

  1. 名称的声明点紧跟在它的完整声明符之后和它的初始化器(如果有的话)之前,除了下面提到的[自赋值示例]。

  2. 非本地名称在声明隐藏它的本地名称之前一直可见。

现在,如果我们假设这是内部“a”的声明点(3.3.1/1)

int a (a);
     ^

那么外部 'a' 应该是可见的,直到定义了内部 'a' 的点 (3.3.1/2)。

问题是在这种情况下,根据 3.1/2,声明就是定义。这意味着应该创建内部“a”。在那之前,我无法从标准中了解外部“a”是否仍然可见。VS2010 假定它是,括号内的所有内容均指外部范围。然而,clang++ 和 g++ 将该行视为自赋值的情况,这会导致未定义的行为。

我不确定哪种方法是正确的,但我发现 VS2010 更加一致:在完全创建内部“a”之前,外部范围仍然可见。

于 2012-05-24T09:24:12.707 回答