【问题标题】:Re-declaring variable inside for loop in C在C中的for循环内重新声明变量
【发布时间】:2018-05-13 23:43:47
【问题描述】:

我在GCC 编译器上使用gcc prog.c -Wall -Wextra -std=gnu11 -pedantic 命令编译了以下程序。我想知道,它工作正常,没有任何警告或错误。

#include <stdio.h>

int main(void)
{
    for (int i = 0; i == 0;  i++) 
    {        
        printf("%d\n", i);
        long int i = 1; // Why doesn't redeclaration error?
        printf("%ld\n", i);
    }
}

为什么编译器不生成重声明变量i错误?

【问题讨论】:

  • 您是否也没有收到int main 未返回值的警告?
  • 根据 n1570(C11 草案)的 5.1.2.2.3:“到达终止 main 函数的 } 返回值 0”(只要返回类型与 int 兼容。 )
  • 在 C99 及更高版本中,为了(被误导?)与 C++98 保持一致,您可以省略 main() 末尾的 return 0;(但仅限于 main() —没有其他函数得到特殊处理),它相当于最后的return 0;。就个人而言,我认为这不是一个好的决定,而且我的政策是不利用许可。不过,我无法阻止其他人这样做——我只能建议他们不要这样做。
  • Scope hiding in C的可能重复
  • @OmG,根本不是那个的副本。

标签: c gcc declaration


【解决方案1】:

来自标准 §6.8.5.5 (N1570)

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

强调

【讨论】:

    【解决方案2】:

    在C语言中,statement的作用域嵌套在for循环init-statement的作用域内。

    根据Cppreference

    在 C++ 中,init 语句的范围和 声明是一回事,在C中statement的范围是嵌套的 在init-statement范围内

    根据stmt

    for 语句

    for ( for-init-statement conditionopt ; expressionopt ) statement
    

    等价于

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

    除了在 for-init-statement 中声明的名称与在条件中声明的名称在同一个声明区域中, 并且除了 continue in 语句(未包含在另一个 迭代语句)将在重新评估之前执行表达式 条件。

    【讨论】:

    • 在回答您的语言律师 C 问题时,您应该停止参考 cppreference。即使对于 C++,它也包含错误。
    • @AnttiHaapala,同意这并不总是作为来源的最佳选择。但在这里它是正确的,并且提出了一个重要的观点,即 C 和 C++ 对此有不同的规则。甚至有针对 C (open-std.org/jtc1/sc22/wg14/www/docs/n2148.htm#dr_466) 的缺陷报告,结果证明这种差异是无意的,但改变它为时已晚。
    • @JensGustedt 实际标准是更好的参考。还有那个博士
    • @AnttiHaapala cppreference 也有一个 C 参考部分,对应的页面是this。 (但我同意这个标准当然更权威)。
    【解决方案3】:

    您必须设置 -Wshadow 才能获得阴影变量的警告。 C 中允许变量阴影。

    但这是一个极端情况。在 for 构造的头部声明的 var 不在括号之外,因为在构造之后它没有范围。

    这是等效的:

    int i;
    for( i = 0; …)
    { … }
    // is is still in scope but wouldn't if declared in the head of for
    

    但是,它也不在括号内。

    for( i = 0; …)
    { 
      int i; // this would be strange, because i is used before it is declared.
      … 
    }
    

    代码的最佳近似替换是这样的:

    {
      int i;
      for( i = 0; …)
      {
      … 
      }
    }  // i loses scope
    

    所以这不是重新声明,而是循环体内的阴影声明。

    【讨论】:

    • 我不认为这是问题所在,但为什么for 声明和{} 内的声明不在同一个声明范围内。
    • for 中声明的标识符位于正文中使用的括号之外。其范围包括for 中的表达式。括号内的标识符的范围没有。
    • @EricPostpischil 确实如此。你为什么这么评论?
    • @AminNegm-Awad:您的回答是“在 for 构造的头部声明的 var 不在括号之外,因为它在构造之后没有范围。”那是错误的。在 for 的头部声明的标识符具有括号外的范围。 for 标头内的部分在括号外。
    • @EricPostpischil 阅读整篇文章,每个人(最后几乎每个人)都应该很清楚,这意味着“只是在括号之外作为 for 前面的声明”。
    【解决方案4】:

    Why compiler doesn't generate redeclaration variable i error?

    来自C Standards#6.2.1p4 Scopes of identifiers

    每个其他标识符的范围由其声明的位置决定(在声明符或类型说明符中)。如果声明标识符的声明符或类型说明符出现在任何块或参数列表之外,则标识符具有文件范围,该范围在翻译单元的末尾终止。如果声明标识符的声明符或类型说明符出现在块内或函数定义中的参数声明列表内,则标识符具有块作用域,该作用域在相关块的末尾终止。如果声明标识符的声明符或类型说明符出现在函数原型(不是函数定义的一部分)的参数声明列表中,则标识符具有函数原型范围,该范围终止于函数声明符的末尾。如果标识符在同一名称空间中指定两个不同的实体,则范围可能重叠。如果是这样,一个实体的范围(内部范围)将严格在另一个实体的范围(外部范围)之前结束。 在内部范围内,标识符指定在内部范围内声明的实体;在外部范围内声明的实体在内部范围内隐藏(不可见)。

    来自C standards#6.8.5p5 Iteration statements

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

    所以,在这段代码中:

    for (int i = 0; i == 0;  i++)
    {
        printf("%d\n", i);
        long int i = 1; // Why doesn't redeclaration error?
        printf("%ld\n", i);
    }
    

    名称为i 的标识符的范围是重叠的,在这个名称空间中,for (int i = 0; i == 0; i++) 中声明的i 具有外部范围,而在循环体long int i = 1; 中声明的具有内部范围

    在循环体内,在这条语句之后:

    long int i = 1;
    

    在外部作用域中声明的i不可见printf() 打印在内部作用域1 中可见的i 的值。

    这种行为也称为变量阴影,当在某个范围内声明的变量与在外部范围内声明的变量同名时,就会发生这种情况。

    C 语言允许变量隐藏,这就是为什么编译器不会为此抛出任何错误。但是,在gcc 编译器中,如果您使用-Wshadow 选项,您将收到一条警告消息-declaration shadows a local variable

    【讨论】:

      【解决方案5】:

      为了进一步验证,我在 Visual Studio 2008 中检查了此代码以获取 prog.c 文件。我发现编译器在 (int i = 0; i == 0; i++) 的行中确实给出了错误。编译器期望 i 的声明位于程序本身的开头。这种行为对于 C 文件是正确的。如果将声明移到程序的开头,则不会出现预期的错误。所有范围相关的问题都已解决。

      如果我将此代码作为 prog.cpp 文件尝试,那么编译器会给出重新声明错误。这也是一种预期行为。

      所以我得出结论,这与 gcc 编译器有关,用于编译/构建 exe 的任何标志/参数是否会导致 gcc 编译器出现这种行为。

      rsp 能否发布 make 文件详细信息以供进一步验证?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-10-11
        • 2016-10-22
        • 2015-08-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多