在 c++ 中隐藏 int 变量的名称

Hiding name of int variable in c++

本文关键字:变量 int c++ 隐藏      更新时间:2023-10-16

出于好奇,我尝试了这个代码,这是由面试问题[*]

int main(int argc, char *argv[])
{
    int a = 1234;
    printf("Outer: %dn", a);
    {
        int a(a);
        printf("Inner: %dn", a);
    }
}

当在Linux(g++ 4.6.3和clang++ 3.0(上编译时,它输出:

Outer: 1234
Inner: -1217375632

但是在Windows(VS2010(上,它打印:

Outer: 1234
Inner: 1234

基本原理是,在第二个"a"变量的复制构造函数完成之前,第一个"a"变量仍然可以访问。但是,我不确定这是标准行为,还是只是(其他(Microsoft怪癖。

知道吗?

[*] 实际问题是:

如何在不使用临时变量或全局变量的情况下,使用包含作用域中同名变量的值初始化作用域内的变量?

{
    // Not at global scope here
    int a = 1234;
    {
        int a;
        // how do you set this a to the value of the containing scope a ?
    }
}

如何在不使用临时变量或全局变量的情况下,使用包含作用域中同名变量的值初始化作用域内的变量?

除非可以显式命名外部作用域,否则无法执行此操作。可以显式命名全局作用域、命名空间作用域和类作用域,但不能显式命名函数或块语句作用域。

<小时 />

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确认这是一个错误,而不是程序触发未定义的行为。

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

如何在不使用临时变量或全局变量的情况下,使用包含作用域中同名变量的值初始化作用域内的变量?

如果您想了解措辞的技术,这很容易。"临时"在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才能使其工作。

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

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

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

如何在不使用临时变量或全局变量的情况下,使用包含作用域中同名变量的值初始化作用域内的变量?

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

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

int a(a);

视觉C++有时会警告您隐藏变量(也许这仅适用于派生类的成员(。 在初始化之前告诉您正在使用值通常也很好,这里就是这种情况。

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

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

3.1 声明和定义 [basic.def]

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

  2. 声明是一个定义,除非...[以下为非相关案例]

3.3.1 声明要点

  1. 名称的声明点紧接在其完整声明符之后和初始值设定项(如果有(之前,除非下面 [自赋值示例] 所述。

  2. 非本地名称
  3. 在声明隐藏它的本地名称之前仍然可见。

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

int a (a);
     ^
那么外部"A">

应该在定义内部"A"的那一点(3.3.1/2(之前可见。

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

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