C程序堆栈

时间:2015-08-22 20:05:18

标签: c

#include<stdio.h>

int* foo() {
  int a = 5;
  return &a;
}

void bar() {
  int a = 7;
}

void foobar() {
    int a = 97;
}

int main() {
    int* p = foo();
    bar();
    foobar();
    printf("%d", *p);

    return 0;
}

我无法理解这种行为背后的概念。 为什么输出总是在foobar函数中的局部变量a的值?

4 个答案:

答案 0 :(得分:3)

这是未定义的行为,因为您返回指向局部变量的指针,该局部变量在其函数范围之外无效。因此,内存中的这一点可能会被重用,因为它似乎就在这里。永远不要返回指向局部变量的指针!

答案 1 :(得分:3)

不能这样做:

int* foo() {
  int a = 5;
  return &a;  /* variable "a" is out of scope once "foo()" returns */
}

这是"undefined behavior"。结果可能不同于环境到环境,编译器到编译器,甚至运行运行。但它总是&#34;垃圾&#34;。

答案 2 :(得分:2)

该程序未定义bahaviour,因为指针p由函数foo

的局部变量的地址初始化
int* p = foo();

退出函数后,局部变量被破坏,指针无效。

你的ptogram总是输出函数foobar的局部变量的值的原因是它们在被调用时似乎使用相同的堆栈帧。因此,它们的局部变量放在堆栈中的相同地址。

如果你将以下列方式更改foo函数

int* foo() {
  static int a = 5;
  return &a;
}

然后程序将输出具有静态存储持续时间的函数的局部变量的值。

答案 3 :(得分:1)

当调用函数时,编译器提供准备堆栈的代码,保存当前堆栈指针并为本地(自动)变量腾出空间。这通常称为功能序言。序言后的堆栈布局或多或少:

+-----------------------------+
|      Parameters if any      |
+-----------------------------+
|      Return address         |
+-----------------------------+
| copy of ESP (stack pointer) |
+-----------------------------+
| Local variables begin here  |
+-----------------------------+
|   ...                       |

现在,如果您有3个将开发相同布局的函数:

      foo()                 bar()                foobar()
+----------------+    +----------------+    +----------------+
| Return address |    | Return address |    | Return address |
+----------------+    +----------------+    +----------------+
|      ESP       |    |      ESP       |    |      ESP       |
+----------------+    +----------------+    +----------------+
|     int a      |    |     int a      |    |     int a      |
+----------------+    +----------------+    +----------------+
|      ...       |    |      ...       |    |      ...       |

如果您在第一个函数a中获得了变量foo()的地址,则当您致电bar()foobar()时,将重复使用相同的地址。在通话结束后访问a,您会在foobar()找到最后写的值。

如果您以这种方式更改功能:

#include<stdio.h>

int* foo() {
  int a = 5;
  return &a;
}

int* bar() {
  int a;
  int b = 7;
  return &b;
}

int* foobar() {
  int a;
  int b;
  int c = 97;
  return &c;
}

int main() {
    int* p1 = foo();
    int* p2 = bar();
    int* p3 = foobar();
    printf("%d %d %d", *p1, *p2, *p3);

    return 0;
}

令人惊讶的是,您将阅读所有值。现在的情况是:

      foo()                 bar()                foobar()
+----------------+    +----------------+    +----------------+
| Return address |    | Return address |    | Return address |
+----------------+    +----------------+    +----------------+
|      ESP       |    |      ESP       |    |      ESP       |
+----------------+    +----------------+    +----------------+
|     int a      |    |     int a      |    |     int a      |
+----------------+    +----------------+    +----------------+
|      ...       |    |     int b      |    |     int b      |
+----------------+    +----------------+    +----------------+
|      ...       |    |      ...       |    |     int c      |
+----------------+    +----------------+    +----------------+
|      ...       |    |      ...       |    |      ...       |

顺便说一下,这是未定义行为的大家庭,而最重要的是错误。自动变量的寿命仅限于其范围,不得在外部使用。

无论如何,堆栈上值的行为通常是稳定的,因为内存管理器会保留堆栈页面的数据(这就是为什么将未初始化的局部变量用作随机值没有意义),但在未来的架构设计中,MM可以丢弃未使用的内存并且不保存它,从而有效地定义了这些内存位置的内容。