我有一个关于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 ++)上会有任何不同。
答案 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保持不变!
然后,调用第二个函数并执行相同的过程:
由于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个。然而,即使程序在您的架构上以这种方式运行,依赖于此也是完全愚蠢的。
如果有的话,这种行为甚至可能是一种障碍,因为从未初始化的变量中获取一致的值可能会掩盖错误。