【问题标题】:Memory Allocation: Why this C program works? [duplicate]内存分配:为什么这个 C 程序有效? [复制]
【发布时间】:2011-09-06 14:37:30
【问题描述】:

可能重复:
Returning the address of local or temporary variable

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;
}

【问题讨论】:

标签: c memory


【解决方案1】:

它“有效”的原因是因为你很幸运。返回指向局部变量的指针是未定义的行为!!你不应该这样做。

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

【讨论】:

  • 说实话,这个简单的案例很有可能“奏效”。如果在调用add 和获取值之间有任何东西操纵堆栈指针,它就会中断,但从技术上讲,该值并没有被破坏——只是被遗忘了。不过,这仍然是一个可怕的想法。一旦函数返回,它就不再拥有堆栈的那一小部分,并且任何函数调用(或信号,或可能的中断等)都可以覆盖该值。根据标准,它是 UB,但在任何在堆栈上传递值的架构中,其行为几乎相同。
  • 我不认为它是随机发生的。这是我的CS课程中的一个问题。在你的机器上试一试,你会得到正确的结果。我想知道这是怎么发生的
  • @Bryon,看看'Undefined Behaviour'的定义,这意味着任何事情都可能发生,如果它愿意,它可以覆盖MBR。
  • @Tony:可以,但不会。 C 规范没有定义会发生什么,所以它说“任何事情都可能发生”。但是你测试的每台机器(假设它使用堆栈作为局部变量)都会做三件事之一:返回正确的值、返回错误的值或段错误。它不会着火——或者覆盖你的 MBR,或者订购一打披萨,或者其他任何东西——除非你依赖重要的东西的价值。
  • @Tony: 另请注意,如果此代码在普通用户运行时覆盖了 MBR,那么无论 C 标准对此主题有何规定,这都是操作系统错误。仅仅因为 C 标准说您的操作系统供应商可以在总线前运行,并不意味着它会。我认为 cHao 的观点只是,虽然它是 UB,因此 C 标准对于它是否有效或为什么有效并没有最微弱的线索,但我们是聪明的人,对典型的 C 实现略知一二,所以我们可以冒险猜测一下在这种情况下,描述用户机器上发生的事情。也就是说,他是怎么走运的。
【解决方案2】:

因为 C 没有垃圾回收,所以当“z”变量超出范围时,实际内存不会发生任何事情。如果编译器愿意,它只是被释放以供另一个变量覆盖。

由于在调用“add”和打印之间没有分配内存,因此该值仍然位于内存中,并且您可以访问它,因为您有它的地址。你“走运了”。

但是,正如 Tony 指出的那样,您永远不应该这样做。它有时会起作用,但一旦你的程序变得更复杂,你就会开始得到虚假值。

【讨论】:

    【解决方案3】:

    没有。您的问题表明对 C 内存模型的工作原理缺乏了解。

    值 z 分配在堆栈上的一个地址,在控制进入 add() 时创建的帧中。然后将 ans_ptr 设置为此内存地址并返回。

    堆栈上的空间将被下一个调用的函数覆盖,但请记住,除非明确告知(例如通过 calloc() 之类的函数),否则 C 永远不会执行内存清理。

    这意味着内存位置 &z 中的值(来自刚刚腾出的堆栈帧)在紧随其后的语句中仍然完好无损,即。 main() 中的 printf() 语句。

    您应该永远不要 永远依赖这种行为 - 只要您在上面添加额外的代码,它就可能会中断。

    【讨论】:

      【解决方案4】:

      答案是:这个程序之所以有效,是因为你很幸运,但它不会花时间背叛,因为你返回的地址不再保留给你,任何人都可以再次使用它。这就像租用房间,制作复制钥匙,释放房间,然后在稍后释放房间后尝试使用复制钥匙进入它。在这种情况下,如果房间是空的并且没有租给其他人,那么你很幸运,否则它可能会让你被警察拘留(不好的事情),如果房间的锁被改变,你会得到一个段错误,所以你可以'不要只相信您在没有获得房间的情况下制作的重复密钥。

      z 是在堆栈中分配的局部变量,其范围与对功能块的特定调用一样长。您返回这样一个局部变量的地址。从函数返回后,块的所有本地地址(在函数调用堆栈帧中分配)可能会用于另一个调用并被覆盖,因此您可能会或可能不会得到您期望的结果。这是未定义的行为,因此这样的操作是不正确的。

      如果您得到正确的输出,那么您很幸运,该内存位置保存的旧值没有被覆盖,但是您的程序可以访问该地址所在的页面,因此您不会收到分段错误错误.

      【讨论】:

        【解决方案5】:

        正如 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;
            ^      ~~~~~~~
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-07
          • 1970-01-01
          • 2023-03-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多