函数返回时堆栈会发生啥? [复制]

Posted

技术标签:

【中文标题】函数返回时堆栈会发生啥? [复制]【英文标题】:What happens to a function's stack when it returns? [duplicate]函数返回时堆栈会发生什么? [复制] 【发布时间】:2013-04-18 06:35:15 【问题描述】:
#include <stdio.h>

void function()

    int stackVar = 10;
    printf("Stack variable = %d\n", stackVar);


int main(void)

    function();
    return 0;

function 返回时的栈帧会发生什么?

【问题讨论】:

1) 您将 指针 *x 初始化为从“function()”返回。 2) 你从未初始化过 *x* *points to*. So printf ("%d", *x)` 正在打印未初始化的数据。 @paulsm4 OP 似乎知道他在做什么。此外,该函数返回一个指向堆栈变量的指针,因此 int* x 在技术上已初始化。 @AnishRam 您的问题中没有提到堆栈帧或特定系统。不是所有的计算机都有堆栈帧,有些计算机没有严格的对齐要求,只是根据需要分配尽可能多的堆栈空间。此外,会发生什么完全取决于系统特定的调用约定。并且局部变量也可以分配在CPU寄存器中,您无法知道。没关系,这是一个常见问题解答。只要接受,如果在其生命周期之外访问,您可以获得变量中的任何随机值(包括正确的结果)。 我只记得this classic post的话题。 @AnishRam 好的。我想您想了解调用堆栈的一般工作原理。那确实是一个不同的问题。但是还有一个问题是你给出了特定的 C 代码,不幸的是它具有未定义的行为,这会将可能的答案拉向不同的方向。无论如何..如果您的问题被关闭并且您认为它不应该关闭,您可以对其进行编辑以使其更具体并向我发送另一条评论;如果看起来合理,我准备投票重新开放。很抱歉给您带来不便。 【参考方案1】:

这是 未定义 行为(相对于实现定义或未指定)。这意味着程序可以随意行为不端,或者不以任何方式取悦。

这在6.2.4 对象的存储持续时间中有详细说明:

1 对象具有决定其生命周期的存储持续时间。共有三个存储 持续时间:静态、自动和分配。 7.20.3 中描述了分配的存储空间。

2 对象的生命周期是程序执行的一部分,在此期间存储是 保证为它保留。一个对象存在,有一个常量地址,并且保留 其在其整个生命周期中的最后存储价值。 如果一个对象在其外部引用 生命周期,行为是未定义的。指针的值变得不确定 它指向的对象到达其生命周期的终点。

3 一个对象,其标识符用外部或内部链接声明,或者用 storage-class 说明符 static 具有静态存储持续时间。它的生命周期是整个 程序的执行及其存储的值仅在程序之前初始化一次 启动。

4 一个对象,其标识符被声明为没有链接且没有存储类 指定的静态具有自动存储期限。

5 对于这样一个没有变长数组类型的对象,它的生命周期会延长 从进入与其关联的块直到该块的执行结束 任何方式。(进入封闭块或调用函数会暂停,但不会结束, 执行当前块。)如果块是递归进入的,则该块的新实例 每次都会创建对象。对象的初始值是不确定的。如果 初始化是为对象指定的,每次声明时都会执行 在执行块时达到;否则,每个值都变得不确定 到达声明的时间。

【讨论】:

您能提供一些引用吗?其实我已经找了好久了。 对故意制造的错误的“引用”?!? @AnishRam:我已经添加了相关的引号。 @paulsm4,我的意思是对这种未定义行为的引用:) 请注意,这是从 C99 标准中引用的。 C90 和 C11 的文字不同,但意思相同。【参考方案2】:

首先,您已经大幅修改了问题,因此其他答案(有点不公平)不再相关。不过,要回答当前的问题:

函数返回时的栈帧会发生什么?

在我看来,您对堆栈的运行方式缺乏总体感觉。所以 - 在这里有点疯狂 - 但会尝试一个可能使它“点击”的类比。您可以将堆栈帧想象成海滩上的波浪。嵌套的函数调用越深,这些函数在参数和局部变量中的数据越多,使用的内存就越多。这就像海浪到达海滩更远的地方。随着作用域的退出,内存被有效地释放——忘记了该内存的用途。海浪也会退去。尽管如此,在程序的整个生命周期中,随着不同的函数序列进入和退出,相同的内存(海滩的水平)被重复使用(在水下)和被遗忘(而不是在水下)。距离海滩最远的部分往往被覆盖的频率最低且持续时间短,而有些则一直待在水下,直到退潮的最弱点......类似的事情,比如没有尾递归优化的递归函数可能会使用大量内存简单地说,但是直接在main() 中创建的堆栈变量会一直保留在那里直到程序终止。

【讨论】:

是的,我知道。我向回答者道歉。那么这是否意味着在输入函数时分配堆栈帧?那么所有这些push 指令都会进行分配吗?所以这个内存使用类似于mallocfree 因为free 只是将内存标记 为空闲(以便以后可以覆盖)? ( @AnishRam:对所有这些都是肯定的。该实现有一个称为“堆栈指针”的东西,它指向“当前堆栈位置”。 push 指令存储一个值并移动堆栈指针。当一个函数返回时,它将堆栈指针放回到它在函数入口处的任何值(也许它也弹出自己的参数,作为调用约定的一个特性)。就是这样。并非每个实现都是相同的(例如 gcc 有 -fsplit-stack,大多数现代操作系统在第一次使用时都在堆栈中而不是在前面),但这就是调用堆栈通常的工作方式。跨度> @AmishRam:为了补充史蒂夫所说的,您通常将一些特定的值压入堆栈 - 这会将堆栈指针移动与该数据大小相对应的量(稍后将其弹出并反转堆栈指针移动),或者编译器计算出许多变量的总大小(例如,在新函数调用开始时所需的所有变量)并在一条指令中将那么多变量添加到堆栈指针,然后使用 a 引用堆栈上的特定变量字节数偏移量。 @AmishRam:“所以这个内存使用类似于 malloc 和 free 因为 free 只是将内存标记为空闲(所以以后可以覆盖它)?”从有限的意义上说,释放的内存在分配之前一直存在,这是完全正确的。当然,malloc 和 free 不能只是增加和减少总堆使用量,因为需要数据的生命周期并没有像堆栈上的变量那样排序。 非常感谢您的回答! @Steve,也谢谢你!【参考方案3】:

未定义的行为。 您正在从函数返回局部变量地址,因为堆栈帧已被破坏(超出范围)。 现在,如果内存(地址)没有被覆盖,那么您将获得相同的值,否则您将获得垃圾。

【讨论】:

关于这一点:...如果内存没有被覆盖,那么您将获得相同的值:您听起来好像毕竟定义了未定义的行为。 栈帧没有被“销毁” @jogojapan ,行为未定义,因为您无法猜测该内存是否可用(即未分配),这就是结果不可预测的原因。 @UmNyobe 一旦函数返回与其对应的堆栈帧就会被销毁..如果不是这种情况,那么将如何释放帧占用的内存 @anshulgarg 但这不是重点。示例:如果编译器意识到代码的某些部分会导致未定义的行为(并且这在 OP 提供的代码中很容易实现),它可以做任何它想做的事情。它实际上不必生成查看局部变量原始地址的机器代码。因此,即使原始内存没有被覆盖,它也会给你带来垃圾,而且还会发生更糟糕的事情。【参考方案4】:

您正在调用未定义的行为。 当您从函数返回时,堆栈帧被销毁(超出范围),您可能会收到您留在函数中的值,但这是巧合。

有关示例,请参阅 Wikipedia,有关文章,请参阅 here。

【讨论】:

当我提出我的问题时,我似乎非常困惑。很抱歉我对它进行了如此彻底的编辑。

以上是关于函数返回时堆栈会发生啥? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

当模型函数在达到回滚或完成之前返回时,插入的记录会发生啥?

堆栈溢出一般是由啥原因导致的?

***Error 啥时候发生? [复制]

如果函数有多个返回语句,javascript 的底层会发生啥?

当您在 php 中调用函数时,内部会发生啥

Oracle RawToHex 函数 - 如果返回值超过 varchar2 限制会发生啥?