为什么这段C代码可以编译通过?

21
#include <stdio.h>
int main() {
    int c = c;
    printf("c is %i\n", c);
    return 0;
}

我正在定义一个名为c的整数变量,并将其值赋给自己。但是这怎么可能编译通过呢?c还没有被初始化,那么它的值怎么能被赋给自己呢?当我运行程序时,会得到c is 0。

我认为编译器生成的汇编代码正在为c变量分配空间(当编译器遇到语句时)。然后它取出该未初始化空间中的任意垃圾值并将其再次赋给c。这就是在发生什么吗?


3
不幸的是,这是允许的 - 通常可以让编译器发出有关此问题的警告,例如使用 g++(但出于某种原因不适用于 gcc),使用 -Wall 标志会产生警告。 - anon
7
@Neil,若要在gcc中强制出现错误,可以使用"-Werror=uninitialized -Winit-self"。 - Matthew Flaschen
5个回答

30

我记得之前在另一个回答中引用过这段话,但是现在找不到了。

C++03 §3.3.1/1:

名称的声明点是在其完整的声明符(第8条)后立即,在其初始化程序(如果有的话)之前...

因此,变量c在初始化部分之前就可以使用。

编辑:抱歉,你问的是C语言的特定内容;虽然我确信那里也有一个等效的行。James McNellis找到了它:

C99 §6.2.1/7:除了结构体、联合体或枚举标签外,任何标识符“在其声明符完成后立即具有作用域”。声明符后面跟着初始化器。


12
C99 §6.2.1/7:任何不是结构体、联合体或枚举类型标记的标识符,“其作用域从其声明符完成后开始。” 声明符后跟初始化程序。 - James McNellis

11

你猜得很对。 int c 在堆栈上为变量推送空间,然后在 c = c 部分读取并重新写入该变量(尽管编译器可能会优化它)。您的编译器将该值作为 0 推入,但不能保证始终如此。


4
很可能它并没有推入0,只是已有的栈帧中恰好包含了0内存。当然,依赖于0是否存在是未定义的。 - Yann Ramin
1
比引用标准更好的答案。 - kirk.burleson
1
@kirk 很多人更喜欢参考标准,这样你就知道它是有保证的行为,而不仅仅是惯例或巧合。 - Michael Mrozek
@Michael - 很多人更喜欢简单的解释。 - kirk.burleson
我不能说我喜欢短语“将空间推入堆栈”。它本身并没有错,但是...也许应该改为“在堆栈上预留空间”或“确保堆栈上有足够的空间”。 - dmckee --- ex-moderator kitten
@dmckee:严格来说,是的,我们移动堆栈指针以确保有空间。不过我觉得把它想象成将空间推入堆栈更简单,而且对于可能不太熟悉C语言的人来说,这也更容易解释。 - me_and

5

使用未初始化的值是未定义行为(§C99 J.2“在其不确定状态下使用具有自动存储期限的对象的值”)。因此,任何事情都可能发生,从鼻妖到c = 0,再到玩Nethack


该值是不确定的,但这并不是未定义行为。鼻妖和Nethack被排除在外。c = 0是可能的(也很可能)。这里的行为方式类似于随机数生成器,虽然我们无法确定结果值,但我们可以确保该值是此语句中唯一未知的事物,符合标准。 - Michael Gazonda

2

c已经被初始化!

虽然这只是一行代码,但实际上它先初始化了c,然后将c赋值给它。你很幸运编译器为你将c初始化为零。


-1... 虽然被误导和错误,但仍然得到了最高票数... 在示例中显示的变量 c 并不是指针,而是分配在堆栈上的。 - gnud
1
不同意 - 根据编译器选项,c 对我来说是未初始化的,这可以通过运行此代码时产生不同的非零输出来证明。 - pilcrow
Andres,我删除了帖子的一部分,因为它很令人困惑。我的意思是说,“c”的标签“指向”某些内存,而不是int*意义上的指针。也许引用是一个更正确的术语? - Donald Miner
1
程序在将c赋值给c之前未对其进行初始化。有时它可能是0,但在不同的条件下它可能是其他任何值。 - Artelius

2

C语言规范不保证变量会被初始化为0,0.0或""或''。

这是编译器的一个特性,你不能相信它会发生。

我总是设置我的IDE/编译器来警告这一点。


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