繁体   English   中英

C在调用函数激活记录时究竟将其实际使用多少堆栈空间?

[英]How much stack space does C exactly use for a function activation record when calling it?

环境:Windows10上的gcc版本6.3.0(MinGW.org GCC-6.3.0-1)

我在命令行中编译并运行代码。

这是我的代码:

#include <stdio.h>  
int func(void){
    int c;
    printf("stack top in func \t%p\n", &c);
    return 1;
}
void main(void)  { 
    int arr[0];
    int i;  
    printf("stack top before func \t%p\n", &i);
    i = func();
    int j;
    printf("stack top after func \t%p\n", &j);
    return;  
}

结果如下:

stack top before func   0061FF2C
stack top in func       0061FEFC
stack top after func    0061FF28

处于运行状态时的堆栈顶部与未达到功能的堆栈顶部之间的间隙大小为48个字节。

然后,我将“ arr”的大小更改为1,结果是:

stack top before func   0061FF28
stack top in func       0061FEFC
stack top after func    0061FF24

差距缩小了,而栈顶在使用时保持不变。差距大小现在是44个字节。

当“ arr”的大小为3时,它停止缩小。

新的间隙大小为52个字节。

那是内存管理的策略吗?

当它可以选择选择使用52个字节并且可以在编译时知道函数调用的变量大小时,使用44个字节有什么好处?

我认为您对堆栈和编译器的工作方式做出了毫无根据的假设。 即:

  1. 在声明变量时就分配了变量,
  2. “最后一个”变量占据了堆栈的“顶部”,
  3. 变量仅占用所需的空间,
  4. 这有一个明确而确定的答案。

关于在C,gcc,x86平台上调用函数(无优化)时发生的情况,这是一个大概的想法:

  1. 参数(如果有)存储在寄存器和/或堆栈中。 详细信息在32位和64位,整数/指针,浮点数和大小不同,参数数量,vararg等不同的结构之间有所不同。
  2. call指令被执行,它将返回地址压入堆栈(由于不同的原因,我认为32位和64位都占用8个字节),并将处理器重定向到新地址。
  3. 在压入BP的原始值(4或8个字节)之后,堆栈指针将保存在BP寄存器中。
  4. 堆栈指针递减足够的字节以容纳所有局部变量。

回来后

  1. BP寄存器的值将覆盖堆栈指针,自动取消第4步。 然后弹出BP的原始值。
  2. 使用ret指令,弹出返回地址并跳到那里。

应当指出,这绝不是普遍的或保证的。 可以优化“简单”功能以跳过步骤3、4和5。原则上,步骤4可以多次发生。 可以对堆栈指针进行额外的处理,例如将其与特定的2的幂次方对齐(例如SSE指令操作数为128的倍数),分配红色区域, alloca函数等。存在许多异常和特殊情况。 更多详细信息将取决于gcc命令行参数或每个发行版的内置默认值。 其他编译器可能遵循略有不同但兼容的约定。 但是,让我们坚持这个模型。

需要注意的重要一点是,所有局部变量通常在步骤4中一起分配,并且占用的大小可能是所需的总大小,也可能是更多。 例如,惯例可能要求编译器确保堆栈指针在任何时候都是16的倍数(以便函数本身可以依靠它),在这种情况下,它会四舍五入到最接近的倍数(关于步骤1到3)采取的措施。 在该区域内,为本地人分配了地址(与BP或SP的偏移量),例如尊重其大小和对齐要求。

您的示例(尤其是main的代码)无法正常工作,因为仅在从f返回之后,编译器才会按照您的意愿为j分配空间。 它与arri一起出现在函数的开头,并且未指定变量的顺序,可以选择变量的顺序,以便可以将它们最好地“打包”到可用空间中,并且int接受32位或64位地址边界。 即使这样做,通过将j的地址作为“ func之后的栈顶”,也将导致计算错误:充其量是“ func 和allocate之后的栈顶”。 通常,“ func之后的栈顶”必须与C调用约定中的“ func之后的栈顶”相同。


为了使您的功能更具体,我建议:

编译后研究装配 godbolt.com上的工具非常godbolt.com这是 gcc 8.2在x86-64中编译的代码 ,如下所示。

堆栈指针应减少16(第6行)加上8(RBP @ 4行的大小),再加上第28行在64位模式下存储返回地址所需的call ,即8。

使用调试器

(gdb) b 11
(gdb) b 4
(gdb) run
Starting program: [redacted]
stack top before func   0x7fffffffd2dc

Breakpoint 1, main () at a.c:11
11      i = func();
(gdb) print $rsp
$1 = (void *) 0x7fffffffd2d0
(gdb) c
Continuing.

Breakpoint 2, func () at a.c:4
4       printf("stack top in func \t%p\n", &c);
(gdb) print $rsp
$2 = (void *) 0x7fffffffd2b0

您可以在此处看到rsp减少了0x20 == 32。

这是因为gcc的堆栈对齐。

在gcc中,堆栈对齐默认为16字节,而至少在我的环境中。 我使用编译选项“ -mpreferred-stack-boundary = 2”将其更改为4个字节,与int的大小相同。

然后,每次我声明一个新的int时,函数顶部的栈都会移动。

感谢JabberwockyKorni的评论,这些评论引入了我以前不知道的新领域。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM