内存分配堆栈

5
在栈中,为main保留内存,我们称之为main函数的堆栈帧。
当我们调用Add函数时,在堆栈顶部保留内存。在Add函数的堆栈帧中,ab是局部指针,c是一个整数,它计算总和,然后我们返回引用。 cAdd函数的局部变量。
现在,当Add函数执行完成后,堆栈中的内存空间也被释放,因此当我们尝试使用指针pmain中访问此地址时,我们尝试访问的基本上是已释放的空间。编译器会发出警告,但为什么它仍然正确打印值5?
答案可能是,机器认为不需要释放内存空间,因为没有更多的函数。但如果我们写另一个函数Hello,则它应该明确地对调用堆栈中Add函数的空间进行释放,但程序仍然打印结果。
Yay     5

在堆中我们需要在释放指针后将其赋值为null,否则我们仍然可以访问它。这与这里的某些内容有关吗?

/* void Hello()
   {
     printf("Yay");
   } */

int* Add(int *a,int *b)
{
  int c=*a+*b;
  return &c;
}

int main()
{
  int a=1,b=4;
  int *p=Add(&a,&b);
  // Hello();
  printf("\t%d",*p);
  return 0;
}

这里的 c 是什么?int c 是什么意思? - Sourav Ghosh
我认为这是出于性能考虑,内存被释放时不会清零,为什么要清零呢?有人对它清零感兴趣吗?如果需要重用它,它只是变得可用而已。你可以尝试使用相同的函数,但将另一个变量的地址传递给它,它可能会覆盖旧的变量。Hello() 函数比 Add() 函数更小。 - Iharob Al Asimi
是的,应该是 int c 和 Add。我已经编辑了那部分内容。 - Stack
更改指针中存储的值对于您是否仍然可以访问其所指向/曾指向的对象没有任何影响。在堆中释放指针后,如果不将其分配为空指针,则可能仍然可以访问它,这是因为指针仍然指向以前分配的内存地址。 - David Schwartz
5个回答

8

假设变量c是本地的int变量,函数调用后访问c是未定义行为,可能会打印预期结果,也可能会做一些意外的事情。

尽管C没有强制要求,但通常它使用堆栈实现。出于性能考虑,当函数返回时,它不会更改堆栈区域。这就是为什么你看到值为51+4的原因。但你绝不能指望这一点。


当你使用第二个函数时,行为仍然是未定义的,所以你可以得到任何输出。实际上,如果你在第二个函数中定义另一个变量并使用它,输出可能会改变。

                       +-----------------+
                       .(local variables).
                       |'''''''''''''''''|
                       |                 |
+----------------+     . PRINTF FUNCTION .
|  c = 42        |     |                 |
|''''''''''''''''|     +-----------------+
|                |     |                 |
.  ADD FUNCTION  .     . HELLO  FUNCTION .
|                |     |                 |
+----------------+     +-----------------+
|  b = 4         |     |  b = 4          |
|''''''''''''''''|     |'''''''''''''''''|
|  a = 1         |     |  a = 1          |
|''''''''''''''''|     |'''''''''''''''''|
|                |     |                 |
.  MAIN FUNCTION .     .  MAIN  FUNCTION .
|                |     |                 |
+----------------+     +-----------------+

在上图中,我试图直观地表示当您在Add函数和Hello函数内部时,堆栈可能会是什么样子。您可以看到,Hello没有干扰为Add函数中的c保留的堆栈内存。
您可以通过将Hello函数重写为以下内容来验证这一点:
void Hello()
{
  int i = 42;
  printf("Yay - %d\n", i);
}

main函数中可能会输出42


您说当一个函数返回时,它会保持栈区域不变,但如果我们有另一个函数,那么 add 函数不应该被释放吗?因为我们遵循 LIFO 原则,所以在 Hello 函数之后,main 函数应该是活动的,但是如果我们在调用栈中有 add 函数,按照 LIFO 原则,它怎么能到达 main 函数呢? - Stack
@Stack 添加了一些细节,希望这对您有用。 - Mohit Jain
2
@MohitJain 先生,最好的解释。+1. :-) - Sourav Ghosh

1
TL;DR 答案是未定义的行为。您无法“理性化”上述执行的任何输出。如果最终打印您的手机号码或邮政编码,请不要感到惊讶。:-)
范围过期后,堆栈位置会发生什么取决于环境。如果不需要,可能不会重用为c分配的堆栈空间,因此您会看到“假定正确”的输出,但仍然是未定义的。在不同的环境中,您可能会看到不同的答案。 Mr. Mothit Jain答案提供了非常详细的解释。

1
为了完全理解这一点,我建议您学习与您的处理器相关的汇编语言,并查看您的代码如何被编译。在这一点上,我认为如果我说从函数返回不会调用任何内存释放函数,它只是调整寄存器,而且在Add和print之间没有做任何改变"释放"堆栈上的值的操作,您将得到该数字。尝试在其中调用一个返回.. pfft我不知道77的函数(但确保将其保存到本地变量中!),您将打印出不同的结果。虽然要学习汇编语言,成为一名合格的程序员。

但是我在printf之前包含了一个Hello函数,所以add函数不应该被释放,因此在堆栈中我们有Hello、main和LIFO被维护吗? - Stack
你现在可以从Mohit的回答中看出原因了。你的hello函数根本没有到达地址。实际上,Mohit的版本似乎也是这样,我认为不会有问题,但如果你将Add函数更改为Hello并将c = 42,那么即使你返回0或其他任何值,你的打印输出也会得到42。 - Pyjong

0

Hello 函数没有将任何新内容放入堆栈中,因为它没有自动变量。因此,之前由 c 占用的内存位置仍然未改变。修改 Hello 以便它修改内存,这样你就可以更改 p 上的值。

void Hello(void)
{
    //This will overwrite the memory previously containing '5' with '3`, the number
    //of characters output by the printf() function.
    int grub = printf("Yay");
}

0
我认为是因为您没有在Hello函数中定义“栈变量”,当我运行以下程序时:
#include <stdio.h>

void Hello()
{
  int tt = 100;
  int tt1 = 5000;
  printf("Yay");
}

int* Add(int *a,int *b)
{
  int c=*a+*b;
  return &c;
}

int main()
{
  int a=1,b=4;
  int *p=Add(&a,&b);
  Hello();
  printf("\t%d",*p);
  return 0;
}

它在我的Linux中打印5000,在我的Mac中打印1,不过我认为它们都是意外的值。

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