在同一个函数中定义一个变量及其静态等价物

17

我不理解以下代码的工作原理:

#include "stdio.h"

int main(void) {
  int i = 3;
  while(i--) {
    static int i = 100;
    i--,
    printf("%d\n", i);
  }
  return 0;
}

使用Clang或GCC编译的代码将打印以下输出:

99
98
97

有人可以解释一下这里发生了什么吗?看起来是在单个指令中实现了两个操作,且不止一次。这是否属于未定义行为?
我在C++中观察到了相同的情况。


1
可能是重复的 https://stackoverflow.com/questions/22120362/shadowing-of-static-global-and-local-identifiers - StoryTeller - Unslander Monica
3
如果你开启编译器标志,你会发现编译器会警告你有一个声明遮蔽了另一个声明。至少GCC会这样做。 - Michi
2
看起来一个指令实现了两个操作,而且不止一次。您能具体说明您的意思以及期望的输出吗? - Gerhardh
也许有点跑题,但为什么设计编译器允许变量遮蔽?我能想到的唯一用例可能是您包含了使用公共名称的 C 库? - sudo rm -rf slash
“一个指令实现两个操作” - 你在哪里看到这样的东西? - AnT stands with Russia
3个回答

24

这不属于未定义行为。

#include "stdio.h"

int main(void) {
  int i = 3; //first i
  while(i--) {
    static int i = 100; //second i
    i--,
    printf("%d\n", i);
  }
  return 0;
}

在 while 循环体内,最好使用本地变量 i(第二个 i)。在检查 while 循环条件时,它不知道循环体内有什么。因此,它会选择第一个 i,这是没有问题的。


8
只是提供一个信息.. static int i = 100; 只执行一次.. 如果它不是静态的,结果将会是99,99,99。 - Sreeragh A R
@SreeraghAR 确实。 - Pranit Kothari
2
@Sreeragh A R:这是误导性的。在C和C++中,i静态地初始化(也称为“在编译时”)。它甚至从未真正执行过一次。而C语言甚至没有“执行”静态对象初始化的概念。即使初始化程序似乎写在函数内部,实际的初始化也发生在其范围之外。 - AnT stands with Russia
你可能是对的。我只是想表达,如果它不是静态的,结果会有所不同。 - Sreeragh A R

11

维基百科对此有非常明确的解释:

在计算机编程中,变量屏蔽是指在某个作用域内(决策块、方法或内部类)声明一个与外部作用域中已有变量同名的变量。在标识符(名称而不是变量)的级别上,这被称为名称屏蔽。这个外部变量被称为被内部变量所屏蔽,而内部标识符则被称为遮蔽外部标识符。

现在,在这个块内部找到了静态变量并对其进行操作,但是while语句中的 i 减少的是在该块之外声明的那个变量。因为作用域不同,使用正确的值 i 没有问题。虽然这是合法的C代码,但不一定是好的编写方式。

事实上,执行gcc -Wshadow progname.c会得到:

progname.c: In function 'main':
progname.c:7:20: warning: declaration of 'i' shadows a previous local [-Wshadow]
         static int i=2;
                    ^
progname.c:5:9: warning: shadowed declaration is here [-Wshadow]
     int i=2;
         ^

来自标准§6.2.1p4

如果一个标识符在同一名称空间中指代了两个不同的实体,那么作用域可能会重叠。如果是这样,一个实体的作用域(内部作用域)将严格地在另一个实体的作用域(外部作用域)之前结束。在内部作用域中,该标识符指定了在内部作用域中声明的实体;在内部作用域中,声明在外部作用域中的实体被隐藏(并且在内部作用域中不可见)。


2

在嵌套作用域内声明同名变量是可行的。编译器将其视为不同的变量,这非常令人困惑,但每次访问的变量是在最内层作用域中声明的变量。在while外部,是int i = 3;,而在内部则是static int i = 100;

#include "stdio.h"

int main(void) {
  int i = 3; // outer i
  while(i--) { // outer i
    static int i = 100; // inner i
    i--, // inner i
    printf("%d\n", i); // inner i
  }
  return 0;
}

如果这是除了主函数之外的另一个函数,那么对它的第二次调用将会产生:
96
95
94

and so on...


如果你打开编译器标志,你会发现编译器会警告你有一个声明遮蔽了另一个声明。至少GCC是这样的。 - Michi
当程序员可能不知道时,它会警告您引用了最内部的变量。该警告解释了这种行为。 - CIsForCookies

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接