C语言中,函数调用是通过栈帧(stack frame)来实现的。栈帧可以被用来传递参数、保存返回信息、存储局部变量等等。栈帧的一般结构如下图:
栈帧结构的两端由两个寄存器来指定。寄存器ebp指向当前的栈帧的底部(高地址),通常叫做帧指针;寄存器esp指向当前的栈帧的顶部(低址地),通常叫做栈指针。在函数执行过程中,栈指针esp会随着数据的入栈和出栈而移动,因此函数中对大部分数据的访问都基于帧指针ebp进行。
栈帧是由高地址向低地址延伸的,例如:
入栈操作:push $eax
等价于:esp = exp - 4; $eax -> [esp]
出栈操作:pop $eax
等价于:[esp] -> $eax; esp = esp - 4;
下面通过源码加以解释:
add.c
- int add(int m, int n)
- {
- int a;
- int b;
- int c;
- a = m;
- b = n;
- c = a + b;
- return c;
- }
- int main()
- {
- int m = 4;
- int n = 5;
- int c;
- c = add(m, n);
- return 0;
- }
编译:
- gcc -S -Wall add.c
对应的汇编代码add.s
- .file "add.c"
- .text
- .globl add
- .type add, @function
- add:
- pushl %ebp #将当前(即main函数)的帧指针ebp入栈。由于新函数需要有自己的栈帧,因此需要将上面函数(main函数)的帧指针ebp(栈底)保存起来;而栈指针esp(即栈顶)不想要保存,因为两 个栈帧是相邻的,所以上一个栈帧的栈顶将会是下一个栈帧的栈底。
- movl %esp, %ebp #main函数的栈指针esp即为add函数的帧指针ebp
- subl $16, %esp #add函数的栈指针减少16,为局部变量分配空间
- movl 8(%ebp), %eax #取得m的值,放入ebp-12,即a的位置
- movl %eax, -12(%ebp)
- movl 12(%ebp), %eax #取得n的值,放入ebp-8,即b的位置
- movl %eax, -8(%ebp)
- movl -8(%ebp), %edx #将n的值放入edx
- movl -12(%ebp), %eax #将m的值放入eax
- addl %edx, %eax #计算n+m,结果放入eax
- movl %eax, -4(%ebp) #将eax的结果放入ebp-4,即c的位置
- movl -4(%ebp), %eax #将最后的结果放入eax
- leave
- ret
- .size add, .-add
- .globl main
- .type main, @function
- main:
- leal 4(%esp), %ecx
- andl $-16, %esp
- pushl -4(%ecx)
- pushl %ebp
- movl %esp, %ebp
- pushl %ecx
- subl $24, %esp
- movl $4, -16(%ebp) #保存m的值
- movl $5, -12(%ebp) #保存n的值
- movl -12(%ebp), %eax
- movl %eax, 4(%esp) #将n的值放入esp+4
- movl -16(%ebp), %eax
- movl %eax, (%esp) #将m的值放入esp
- call add #调用add函数
- movl %eax, -8(%ebp) #从eax取得返回值,放入ebp-8
- movl $0, %eax
- addl $24, %esp
- popl %ecx
- popl %ebp
- leal -4(%ecx), %esp
- ret
- .size main, .-main
- .ident "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"
- .section .note.GNU-stack,"",@progbits
- <span style="font-size:14px;"></span>
- <span style="font-size:14px;"></span>
- <span style="font-size:14px;">这样编译出来的汇编代码是没有经过优化的,如果编译时选择了优化选项,即</span>
- gcc -S -Wall -O2 add.c
则产生出来的汇编代码如下:
- .file "add.c"
- .text
- .p2align 4,,15
- .globl add
- .type add, @function
- add:
- pushl %ebp
- movl %esp, %ebp
- movl 12(%ebp), %eax
- addl 8(%ebp), %eax
- popl %ebp
- ret
- .size add, .-add
- .p2align 4,,15
- .globl main
- .type main, @function
- main:
- leal 4(%esp), %ecx
- andl $-16, %esp
- pushl -4(%ecx)
- xorl %eax, %eax
- pushl %ebp
- movl %esp, %ebp
- pushl %ecx
- popl %ecx
- popl %ebp
- leal -4(%ecx), %esp
- ret
- .size main, .-main
- .ident "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"
- .section .note.GNU-stack,"",@progbits
从这里也可以看出优化到底做了哪些工作,以及优化前后的效率比较。