reference:
测试代码
#include
int foo_2(int c) {
int array[4];
array[1] = c;
array[3] = c* c;
return array[3] - array[1];
}
int foo(int a, int b) {
int array[10];
array[1] = a;
array[9] = foo_2(b);
return array[1] * array[9];
}
int main() {
printf("%d\n", foo(1,2));
}
执行结果
$ gcc -c -fstack-usage stack-usage-example.c
$ cat stack-usage-example.su
example/stack-usage/stack-usage-example.c:3:5:foo_2 64 static
example/stack-usage/stack-usage-example.c:10:5:foo 80 static
example/stack-usage/stack-usage-example.c:18:5:main 16 static
我们来详细分析为什么 .su
文件中显示的栈大小是 64、80 和 16 字节,这与 int
是 4 字节(在大多数现代系统上,包括你的 Ubuntu 22.04)看似矛盾。
foo_2
的栈使用(64 字节)int foo_2(int c) {
int array[4]; // 4个int = 16字节
array[1] = c;
array[3] = c * c;
return array[3] - array[1];
}
array[4]
:4 * sizeof(int) = 4 * 4 = 16
字节c
(参数):4
字节(通过寄存器传递,通常不占用栈)16
字节(仅 array[4]
).su
报告:64 字节为什么是 64 字节?
GCC 在计算栈大小时会考虑以下额外开销:
rbp
, rbx
等)需要保存在栈上。rbp
,8 字节)也会占用栈空间。|-------------------|
| 保存的寄存器 | // 可选,如 rbx, r12-r15
| 对齐填充 | // 确保 16 字节对齐
| array[4] (16字节) |
| 帧指针 (8字节) | // 如果启用帧指针(-fno-omit-frame-pointer)
| 返回地址 (8字节) |
|-------------------|
实际栈帧可能被填充到 64 字节以满足对齐和调试需求。
foo
的栈使用(80 字节)int foo(int a, int b) {
int array[10]; // 10个int = 40字节
array[1] = a;
array[9] = foo_2(b);
return array[1] * array[9];
}
array[10]
:10 * 4 = 40
字节a
, b
(参数):通过寄存器传递(不占用栈)40
字节.su
报告:80 字节原因:
main
的栈使用(16 字节)int main() {
printf("%d\n", foo(1,2));
}
main
本身没有局部变量。foo(1,2)
通过寄存器传递。.su
报告:16 字节原因:
int
确实是 4 字节,但栈使用不仅由局部变量决定。-O2
),GCC 可能会减少栈使用(例如省略帧指针)。你可以通过汇编代码验证栈分配。编译时添加 -S
生成汇编:
gcc -S -fstack-usage stack-usage-example.c
查看 foo_2
的汇编(片段):
foo_2:
pushq %rbp ; 保存帧指针(8字节)
movq %rsp, %rbp ; 设置新帧指针
subq $48, %rsp ; 分配栈空间(48 + 16 = 64字节)
movl %edi, -20(%rbp) ; 参数 `c` 保存到栈
; ...
这里 subq $48, %rsp
分配了 48 字节,加上 pushq %rbp
的 8 字节和隐含的 8 字节返回地址,总共 64 字节。
-O2
或 -Os
会减少栈帧大小(例如省略帧指针):gcc -Os -fstack-usage stack-usage-example.c
.su
文件中的值可能会显著减小。static int array[10]; // 不再占用栈
__attribute__((aligned(8)))
降低对齐要求(需谨慎)。.su
文件中的栈大小是编译器根据 ABI 规则、对齐要求和调试需求综合计算的结果。➜ /home/mi/local/p65/1 checkStackUsage.py --help
Usage: /home/mi/local/checkStackUsage/checkStackUsage.py [-cross PREFIX] ELFbinary root_path_for_su_files [functions...]
where the default prefix is:
arm-eabi- for ARM binaries
sparc-rtems5- for SPARC binaries
(no prefix) for x86/amd64 binaries
Note that if you use '-cross', SPARC opcodes are assumed.
➜ /home/mi/local/p65/1
➜ /home/mi/local/p65/1 checkStackUsage.py a.out ./
-18446744069414584304: _start (_start(-18446744069414584304))
0: deregister_tm_clones (deregister_tm_clones(0))
0: register_tm_clones (register_tm_clones(0))
0: __do_global_dtors_aux ()
0: frame_dummy (frame_dummy(0))
16: _init (_init(16))
16: _fini (_fini(16))
64: foo_2 (foo_2(64))
144: foo (foo(80),foo_2(64))
160: main (main(16),foo(80),foo_2(64))