内存分配:为什么这个C程序能够正常工作?

3

可能有重复:
返回局部或临时变量的地址

add函数的实现是错误的。它应该返回一个值而不是一个指针。 当打印ans和*ans_ptr并且程序甚至给出正确的结果时,为什么没有任何错误?我猜测z变量已经超出了作用域,应该会出现段错误。

#include <stdio.h>

int * add(int x, int y) {
    int z = x + y;
    int *ans_ptr = &z;
    return ans_ptr;
}

int main() {
    int ans = *(add(1, 2));
    int *ans_ptr = add(1, 2);

    printf("%d\n", *ans_ptr);
    printf("%d\n", ans);

    return 0;
}

3
阅读获得1845个赞的答案:https://dev59.com/ZWw15IYBdhLWcg3wuuCn - Nordic Mainframe
5个回答

9

之所以它“有效”,是因为你运气好。返回本地变量的指针是未定义行为!你不应该这样做。

int * add(int x, int y) {
    int z = x + y; //z is a local variable in this stack frame
    int *ans_ptr = &z; // ans_ptr points to z
    return ans_ptr;
}


// at return of function, z is destroyed, so what does ans_ptr point to? No one knows.  UB results

我不认为这是随机发生的。这是我计算机科学课程中的一个问题。在您的计算机上尝试一下,您可以得到正确的结果。我想知道这是如何发生的。 - Byron
1
@Bryon,看一下“未定义行为”的定义,它意味着任何事情都可能发生,如果它想的话,它甚至可以覆盖MBR。 - Tony The Lion
1
@Tony:还要注意的是,如果这段代码在普通用户运行时覆盖了MBR,那么无论C标准对此有何说法,都是操作系统的错误。仅仅因为C标准说你的操作系统供应商可以在公共汽车前面奔跑,并不意味着它会这样做。我认为cHao的观点只是尽管这是未定义行为,因此C标准对其是否以及为什么起作用毫不知情,但我们是聪明的人类,对典型的C实现有一些了解,因此我们可以猜测描述发生在用户机器上的事情,就像这次幸运的发生方式一样。也就是说,*他如何获得好运。 - Steve Jessop
@Steve:没错。除非编译器是由一个虐待狂编写的,否则行为在某种程度上是可以预测的。在你的程序中依赖这种行为仍然是一个非常糟糕的想法,但在事后解释为什么某些东西“起作用”或者没有“起作用”时可能会有用。 - cHao
@Tony 这个问题是我在操作系统课程中的教授提出的。我认为我的教授有意创造这个问题,背后应该有原因。这个程序在所有的C编译器中都能运行,我不认为这只是运气。 - Byron
显示剩余2条评论

3
由于C语言没有垃圾回收机制,当"z"变量超出范围时,实际内存不会发生任何变化。如果编译器愿意,它只是被释放出来供另一个变量覆盖。
由于在调用"add"和打印之间没有分配任何内存,所以该值仍然存在于内存中,您可以访问它,因��您有它的地址。你“走了运”。
然而,正如Tony指出的那样,您绝不能这样做。它有时候可能有效,但一旦程序变得更加复杂,就会出现错误的值。

0

不行。你的问题显示出了你对 C 内存模型如何工作的基本理解的缺乏。

变量 z 的值是在栈上分配空间的,在控制流进入 add() 函数时创建的帧中。然后将 ans_ptr 设置为这个内存地址并返回它。

下一个调用的函数会覆盖栈上的这段空间,但请记住,C 永远不会执行内存清理操作,除非显式告知(例如通过类似 calloc() 的函数)。

这意味着,紧随其后的语句(即 main() 函数中的 printf() 语句)仍然能够访问前面刚刚释放掉的栈帧中 &z 这个内存地址所指向的值。

你绝对不应该依赖这种行为,因为一旦你在上述代码中添加其他代码,它很可能就会失效。


0
答案是:这个程序之所以能够工作,是因为你很幸运,但它很快就会背叛你,因为你返回的地址不再属于你,任何人都可以再次使用它。这就像租房间,复制钥匙,退房后,你在某个时间试图用复制的钥匙进入房间。如果房间空着且没有被租给别人,那么你很幸运,否则可能会让你陷入警察的手中(糟糕的事情),如果房间的锁被更换了,你会得到一个段错误,因此你不能仅仅依靠未获得房间的复制钥匙。
变量z是在堆栈中分配的局部变量,其作用域只限于特定函数块的调用。你返回了这样一个局部变量的地址。一旦从函数返回,所有在函数调用堆栈帧中分配的块本地地址可能被用于另一个调用并被覆盖,因此你可能得到或者不得到你期望的结果。这是未定义行为,因此这样的操作是不正确的。
如果你得到了正确的输出,那么你很幸运,因为该内存位置所保存的旧值没有被覆盖,但你的程序可以访问地址所在的页面,因此你不会得到分段错误。

0
一个快速测试显示,正如OP指出的那样,无论是GCC 4.3还是MSVC 10都没有提供任何警告。但是Clang静态分析器确实提供了警告:
ccc-analyzer -c foo.c
...
ANALYZE: foo.c add
foo.c:6:5: warning: Address of stack memory associated with local 
                    variable 'z' returned to caller
    return ans_ptr;
    ^      ~~~~~~~

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