C行为(非初始化值)

时间:2011-11-01 18:23:04

标签: c

我有一个关于C代码的问题。

#include <stdio.h>

void foo(void){
        int a;
        printf("%d\n",a);
}

void bar(void){
        int a = 42;
}

int main(void){
        bar();foo();
}

显然我应该在编译结束时获得42。 http://www.slideshare.net/olvemaudal/deep-c,幻灯片#126。但是当我在我的机器上编译它时,我得到了垃圾值(gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5))

我是否需要关闭一些优化?或者幻灯片的作者是错误的吗?有人可以解释代码的结果吗?如果它是42,你能解释一下如何/为什么?此外,结果在C ++编译器(g ++)上会有任何不同。

8 个答案:

答案 0 :(得分:3)

不知道你会得到什么。假设我猜测第二个堆栈帧将与最后一个堆栈帧位于同一位置,而var a是第一个变量,它应该被赋予与前一个函数堆栈帧中的a相同的位置。但这不是定义的行为。

答案 1 :(得分:2)

访问未初始化的变量只是未定义的行为 未定义的行为意味着标准没有指定在某些情况下的行为,包括此行为。编译器甚至可能“让恶魔飞出你的鼻子”(关于UB的流行笑话:))

在该文章中该程序有效,因为bar的{​​{1}}和a的{​​{1}}被放置在同一个内存位置,但这完全没有保险,而你应 永远不要依赖此类行为!

答案 2 :(得分:2)

您可以尝试获取所需结果的内容(值从bar传播到foo):

  • 也许变量a在CPU寄存器中分配而不在堆栈中。为了强制在堆栈上进行分配,请尝试以某种方式使用其地址(&a)。
  • 也许编译器会生成一些代码,用调试模式(0xcdcdcdcd)将未初始化的变量填充到堆栈上 - 我认为gcc不会这样做,但也许我错了?
  • 检查编译器生成的机器代码 - 这是最终方式

答案 3 :(得分:2)

当你调用一个函数时,CPU首先在堆栈上推送函数参数,在堆栈上推送返回地址,然后继续跳转到函数的代码。 在那里,它通过在堆栈上推送它来保存旧堆栈位置(功能序言)。 接下来,它在堆栈上再次分配 a 变量,并将其值设置为42。

在退出函数(函数结尾)时,CPU只需将堆栈指针移回,就可以从堆栈中删除 a 变量,保存旧堆栈位置得到那个旧位置。存储在内存中的值42保持不变

然后,调用第二个函数并执行相同的过程:

  • 推送参数(我们跳过此步骤,因为没有参数)
  • 推送返回地址(在内存中存储其他函数的返回地址的位置)
  • 推送堆栈位置(与返回地址相同)
  • 分配 a 变量,该变量在内存中与bar()函数中的 a 变量完全相同!

由于bar的 a 变量初始化为42,该值仍然存在,而foo的 a 通过指向内存中的相同位置来“继承”它。

在调用bar()之前进行堆栈:

[someThings]<=StackPointer

在bar()中堆叠:

[someThings][returnAddress][oldStackPointer][a = 42]<=StackPointer

在bar()之后和foo()之前堆叠:

[someThings]<-StackPointer {oldStackPointer}{a = 42}

在foo()中堆叠:

[someThings][newRetAddress][oldStackPointer][a (= 42 as the value was here before)]<=StackPointer

所以,是的,幻灯片的作者是正确的,那些说他错了的人实际上是错的:) (请注意,在没有优化的情况下编译代码很重要)

答案 4 :(得分:1)

正如其他人所说,这是未定义的行为。

但是,作者部分正确,在某些体系结构中,局部变量的地址将相同,因此foo将读取bar留在那里的数据。

# gcc x.c; ./a.out
42

这是一种有趣的方式来展示幕后的内容,但不依赖于它,它的架构,编译器和优化依赖:

# gcc -O2 x.c; ./a.out
0
顺便说一句,如果您阅读下一张幻灯片,您将获得所有答案......

答案 5 :(得分:1)

该代码的作者正在制作一个很多特定于平台的假设,这些假设一般都不成立。他假设局部变量以特定方式存储在内存中,并且这些位置不会在函数调用之间被覆盖。一般来说,他错了。

这是一个教科书示例未定义的行为 - 它是错误的代码,并且语言标准没有要求编译器执行任何特定的操作。

答案 6 :(得分:0)

你的两个'a'变量是本地的,它们包含在里面。 foo()中'a'占用的内存空间与bar中的'a'不是相同的内存空间。因此,当你初始化bar中的'a'时,当函数返回时,42将被抛弃,并且永远不会影响foo中的a。

答案 7 :(得分:0)

由于您正在访问未初始化的变量,因此代码的行为未定义。

话虽如此,该程序很可能在我熟悉的架构上打印42个。然而,即使程序在您的架构上以这种方式运行,依赖于此也是完全愚蠢的。

如果有的话,这种行为甚至可能是一种障碍,因为从未初始化的变量中获取一致的值可能会掩盖错误。