【问题标题】:Redeclaration of a variable in a for-loop in C++在 C++ 中的 for 循环中重新声明变量
【发布时间】:2012-09-10 12:13:07
【问题描述】:

当尝试为多个平台编译以下(简化的)代码时,我发现它在某些平台上失败了,即 IBM 的 xlC_r。进一步的调查发现,它在 comeau 和 clang 上也失败了。使用 g++ 和 Solaris 的 CC 编译成功。

代码如下:

int main()
{
    int a1[1];
    bool a2[1];

    for (int *it = a1, *end = a1+1; it != end; ++it) {
        //...
        bool *jt = a2, *end = a2+1;
        //...
    }
}

xlC_r 错误:

"main.cpp", line 8.25: 1540-0400 (S) "end" has a conflicting declaration.
"main.cpp", line 6.25: 1540-0425 (I) "end" is defined on line 6 of "main.cpp".

clang 错误:

main.cpp:8:25: error: redefinition of 'end' with a different type
        bool *jt = a2, *end = a2+1;
                        ^
main.cpp:6:25: note: previous definition is here
    for (int *it = a1, *end = a1+1; it != end; ++it) {
                        ^

comeau 错误:

"ComeauTest.c", line 8: error: "end", declared in for-loop initialization, may not
          be redeclared in this scope
          bool *jt = a2, *end = a2+1;
                          ^

问题是为什么这是一个错误?

查看 2003 年标准,它说以下 (6.5.3):

The for statement
    for ( for-init-statement; condition; expression ) statement
is equivalent to
    {
        for-init-statement;
        while ( condition ) {
            statement;
            expression;
        }
    }
except that names declared in the for-init-statement are in the same
declarative-region as those declared in condition

这里没有在条件中声明名称。

此外,它说 (6.5.1):

When the condition of a while statement is a declaration, the scope
of the variable that is declared extends from its point of declaration
(3.3.1) to the end of the while statement. A while statement of the form
    while (T t = x) statement
is equivalent to
    label:
    {
        T t = x;
        if (t) {
            statement;
            goto label;
        }
    }

同样,我不确定这是否相关,因为条件中没有声明。因此,鉴于 6.5.3 的等效重写,我的代码应该与以下内容相同:

int main()
{
    int a1[1];
    bool a2[1];

    {
        int *it = a1, *end = a1+1;
        while (it != end) {
            //...
            bool *jt = a2, *end = a2+1;
            //...
            ++it;
        }
    }
}

这显然会允许重新声明 end。

【问题讨论】:

  • 好问题。 Comeau 的错误包括“ComeauTest.c”这一事实似乎很可疑。您能否检查__cplusplus 以确保它是在 C++ 模式下编译的?
  • 实际上,仔细观察,bool 的使用应该会在 C 模式下引发编译器错误,因为您没有包含 <stdbool.h>。所以看起来它被编译为 C++,尽管有文件名。
  • 很少(实际上不止几个)年前,编译器使for 变量在for 之外可见是很常见的。 MSVC 6 或 5 做到了这一点。
  • 你为什么要这样写?使代码更难阅读
  • 为什么?因为它是重构的结果。显然我现在已经改变了它,但我很好奇为什么 IBM 拒绝看起来像有效的代码,更奇怪的是 comeau 和 clang 也拒绝它。

标签: c++ for-loop declaration


【解决方案1】:

标准有些模糊。您引用的代码等效于while 循环意味着存在一个内部范围,循环内的声明可以隐藏条件中的声明;但是标准也说(引用 C++11,因为我手边没有 C++03):

6.4/2 条件规则适用于选择语句和forwhile 语句

6.4/3 如果名称在由条件控制的子语句的最外层块中重新声明,则重新声明该名称的声明格式不正确。

6.5.3/1 在 for-init-statement 中声明的名称与在条件中声明的名称在同一声明区域中

它们之间的含义是不能重新声明名称。

该语言的较旧(1998 年之前)版本将 for-init-statement 中的声明放入循环外的声明区域。这意味着您的代码将是有效的,但这不会:

for (int i = ...; ...; ...) {...}
for (int i = ...; ...; ...) {...}  // error: redeclaration of i

【讨论】:

  • @hvd:也许你是对的。我需要更详细地阅读标准。
  • 这似乎更相关,很好的发现。
  • 6.4/3 "条件中的声明引入的名称(由类型说明符序列或条件的声明符引入)在其声明点的范围内,直到结束条件控制的子语句。如果在条件控制的子语句的最外层块中重新声明名称,则重新声明名称的声明格式错误“但不是condition,而是@ 987654326@.
  • @Daryl: "for-init-statement 中声明的名称与条件中声明的名称在同一声明区域中"
【解决方案2】:

我认为代码是正确的。 IMO,问题出在大括号上。注意for语句定义为:

for (for-init-statement; 条件; 表达式) 语句

循环体没有大括号,它们是在使用复合语句时添加的。但是复合语句增加了自己的声明区域,所以内部声明不应该和for-init-statement冲突。

以下代码使用 clang 和 G++ 编译正常(注意双括号):

for (int *it = a1, *end = a1+1; it != end; ++it) {{
    //...
    bool *jt = a2, *end = a2+1;
    //...
}}

我的猜测是,clang 编译器尝试优化,好像循环被定义为:

for (for-init-statement; condition; expression) { statement-seq }

随着含义的细微变化:两个声明区域融合在一起。

然而,第二次,即使它根本没有使用大括号:

for (int x=0; ;)
    char x;

它应该可以正确编译。来自 C++ 草案 6.5,par。 2:

迭代语句中的子语句隐式定义了块范围。

因此,char x; 本身(隐式)定义了一个块范围,并且不应发生冲突的声明。

【讨论】:

  • 确实如此。复合语句是一种语句,其中包含 (6.3) A compound statement defines a local scope (3.3).
【解决方案3】:

当前版本的标准对此很明确:

6.5 迭代语句[stmt.iter]

2 - iteration-statement 中的子语句 [例如,for 循环] 隐式定义了一个块范围 (3.3),每次通过循环进入和退出。

C 也有类似的规则:

6.8.5 迭代语句

语义

5 - 迭代语句是一个块,其范围是其范围的严格子集 封闭块。循环体也是一个块,其范围是范围的严格子集 迭代语句。

【讨论】:

    【解决方案4】:

    一些通常较旧的编译器使在 for 循环中声明的变量在循环范围之外可见。

    为了让所有编译器使用更新(和更好)的方式运行,声明如下宏:

    // In older compilers, variables declared in a for loop statement
    // are in the scope of the code level right outside the for loop.
    // Newer compilers very sensibly limit the scope to inside the
    // loop only. For compilers which don't do this, we can spoof it
    // with this macro:
    #ifdef FOR_LOOP_VARS_NEED_LOCAL_SCOPE
       #define for if(0); else for
    #endif
    

    然后为每个具有旧行为的编译器定义 FOR_LOOP_VARS_NEED_LOCAL_SCOPE。例如,您将如何为 MSVC

    #ifdef _MSC_VER
       #if _MSC_VER < 1400   //  earlier than MSVC8
          #define FOR_LOOP_VARS_NEED_LOCAL_SCOPE
       #endif
    #endif
    

    【讨论】:

      【解决方案5】:

      我在这里聚会有点晚了,但我认为 C++11 标准中的这段话最清楚地不允许这样做:

      3.3.3 块范围 [basic.scope.local]

      4 - 在 for-init 语句、for-range-declaration 中声明的名称,以及在 if、while、for、 和 switch 语句是 if、while、for 或 switch 语句的本地语句(包括受控 语句),并且不得在该语句的后续条件或最外层重新声明 受控语句的块(或者,对于 if 语句,任何最外层的块);见 6.4。

      【讨论】:

        猜你喜欢
        • 2018-05-13
        • 2018-10-11
        • 2016-10-22
        • 1970-01-01
        • 1970-01-01
        • 2021-08-29
        • 2016-02-23
        相关资源
        最近更新 更多