假设我们创建了一个用户函数 void func()。 func 的堆栈帧在函数调用时分配,只要在 main 等其他函数中调用它。
与 printf 相同吗? printf 也会占用堆栈帧吗?
答案 0 :(得分:6)
前提:堆栈帧只是许多C实现的实现细节(具体来说,它通常在x86上使用);它既不是C标准所要求的东西(C标准不知道堆栈的东西),也不是所有实现都做的事情(甚至不是,它甚至可能取决于编译器标志/优化器考虑因素)。
现在,库函数当然最终只是已经编译到标准库中的常规函数,因此在这方面没有什么特别之处:在使用堆栈帧的实现中,每当执行函数调用时,被调用的函数集它的堆栈帧(除非它是内联的或者它是完全无关紧要的,但对于像printf
这样的库函数通常不会发生。)
但是,没有什么可担心的 - 当函数返回时,新的堆栈帧被丢弃并且它的堆栈空间再次可用,所以你不必小心不要调用太多的函数。
答案 1 :(得分:2)
函数的堆栈帧由该函数保留;当然,没有其他函数可以知道另一个函数的堆栈使用需求。
因此,当您从func()
调用main()
时,func()
内的代码(有时称为“前导码”)将保留该函数所需的堆栈空间。
是的,所有功能都适用,包括printf()
。
答案 2 :(得分:1)
这是一个带有main
的小代码,可调用名为func
的简单函数。
我们可以使用gcc main.c -S main.s
编译此c代码。 main.s应为汇编输出。
在程序集中,您可能会看到CPU指令:push,pop,leave,enter。这些指令负责堆栈帧管理。
如果您使用gcc与其他选项进行编译,您可能会注意到一些堆栈管理差异。
#include <stdio.h>
int func(void);
int func(void)
{
int i;
for(i=0;i<100;i++)
printf("%d",i);
return i;
}
int main(void)
{
return func();
}
GCC编译器程序集输出
.file "main.c"
.section .rodata
.LC0:
.string "%d"
.text
.globl func
.type func, @function
func:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $0, -4(%rbp)
jmp .L2
.L3:
movl -4(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
addl $1, -4(%rbp)
.L2:
cmpl $99, -4(%rbp)
jle .L3
movl -4(%rbp), %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size func, .-func
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
call func
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
.section .note.GNU-stack,"",@progbits
这里是函数func
(objdump main.o -S
)的objdump输出。 main.o获得者:gcc main.c -o main.o
000000000040052d <func>:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 10 sub $0x10,%rsp
400535: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
40053c: eb 18 jmp 400556 <func+0x29>
40053e: 8b 45 fc mov -0x4(%rbp),%eax
400541: 89 c6 mov %eax,%esi
400543: bf f4 05 40 00 mov $0x4005f4,%edi
400548: b8 00 00 00 00 mov $0x0,%eax
40054d: e8 be fe ff ff callq 400410 <printf@plt>
400552: 83 45 fc 01 addl $0x1,-0x4(%rbp)
400556: 83 7d fc 63 cmpl $0x63,-0x4(%rbp)
40055a: 7e e2 jle 40053e <func+0x11>
40055c: 8b 45 fc mov -0x4(%rbp),%eax
40055f: c9 leaveq
400560: c3 retq
0000000000400561 <main>:
400561: 55 push %rbp
400562: 48 89 e5 mov %rsp,%rbp
400565: e8 c3 ff ff ff callq 40052d <func>
40056a: 5d pop %rbp
40056b: c3 retq
40056c: 0f 1f 40 00 nopl 0x0(%rax)
答案 3 :(得分:1)
取决于实施。所有标准都没有要求尾部函数调用来使用堆栈帧,但通常这些实现都会使用一个帧。
如此正确就是说
用于func的堆栈帧是通过函数调用分配的 在一些其他功能中调用,例如main
ONLY in some implementations