C语言是如何进行函数调用的,换句话说C语言进行函数调用时都发生了些什么
先写一段简单的C语言程序(不传参)
#include <iostream>
int test(){
int a=0;
return 0;
}
int main()
{
test();
}
这里我们简单的调用了一个函数test,我们对其进行反汇编,查看汇编代码,我用的编译器是gcc,使用clang会有不同的结果但都是大同小异
test():
push rbp
mov rbp, rsp
mov DWORD PTR -4[rbp], 0
mov eax, 0
pop rbp
ret
main:
push rbp
mov rbp, rsp
call test()
mov eax, 0
pop rbp
ret
首先我们知道,rbp是栈的基指针,rsp是栈的顶部的指针。人话说rsp、rbp这两个指针夹着栈。
在调用的时候执行了一条指令
push rbp
也就是把rbp入栈,就是先要把rbp的值暂时存起来,以后要用到。接着是
mov rbp, rsp
将rsp赋值给rbp,也就是将rbp指针移动至rsp。这样做是为了给test函数开辟一个属于他自己的栈。
我会搞一个动画、可能会直观一些
首先说明32位、64位下的函数传参有一些不同
传参的情况下(64位)
int test(int b,int c,int d,int e,int f, int g,int h,int i){
b=1;
c=2;
d=3;
e=4;
f=5;
g=6;
h=7;
i=8;
}
int main()
{
int b=0;
test(1,2,3,4,5,6,7,8);
}
汇编如下
test:
push rbp
mov rbp, rsp
mov DWORD PTR -4[rbp], edi
mov DWORD PTR -8[rbp], esi
mov DWORD PTR -12[rbp], edx
mov DWORD PTR -16[rbp], ecx
mov DWORD PTR -20[rbp], r8d
mov DWORD PTR -24[rbp], r9d
mov DWORD PTR -4[rbp], 1
mov DWORD PTR -8[rbp], 2
mov DWORD PTR -12[rbp], 3
mov DWORD PTR -16[rbp], 4
mov DWORD PTR -20[rbp], 5
mov DWORD PTR -24[rbp], 6
mov DWORD PTR 16[rbp], 7
mov DWORD PTR 24[rbp], 8
nop
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR -4[rbp], 0
push 8
push 7
mov r9d, 6
mov r8d, 5
mov ecx, 4
mov edx, 3
mov esi, 2
mov edi, 1
call test
add rsp, 16
mov eax, 0
leave
ret
可以看到传参的顺序是从右到左的,至于为什么要这样传参是因为栈的性质,先进后出第一个参数最后入栈,那么取出的时候是第一个取出的,这样设计的话就可以支持可变参数了。
然后在看这一段
mov r9d, 6
mov r8d, 5
mov ecx, 4
mov edx, 3
mov esi, 2
mov edi, 1
这是主调函数(就是调用其他函数的那个函数)把参数放到了寄存器里,是这样的,在64位C程序中函数调用优先使用寄存器(快)64位CPU的寄存器很多的可以看到顺序
- edi(rdi)
- esi(rsi)
- edx(rdx)
- ecx(rcx)
- r8d
- r9d
如果参数更多的话,使用栈传参。
但是即使他用了寄存器传参他也一定会放到栈里的,这个操作是在test函数里实现的
test:
push rbp
mov rbp, rsp
mov DWORD PTR -4[rbp], edi
mov DWORD PTR -8[rbp], esi
mov DWORD PTR -12[rbp], edx
mov DWORD PTR -16[rbp], ecx
mov DWORD PTR -20[rbp], r8d
mov DWORD PTR -24[rbp], r9d
这一段。到这里你一定会感到疑惑,为什么
mov DWORD PTR 16[rbp], 7
mov DWORD PTR 24[rbp], 8
这两行和其他两行不一样的说
都是int 前面的占4个字节,这两个占8个字节,并且前面的是减,后面的是加。
然后还是从16开始的,好奇怪!
思考
- rbp寄存器的大小.
- pc寄存器大小.
- 再想想为什么函数调用后能回到调用它的地方.
如果想通了这两件事,问题就迎刃而解了,如果没想明白,下一篇文章将会解答
至于为什么下面是占据8个字节全因为push指令,他一次会压入8个字节(64位cpu),所以就要配合人家
然后为什么上面是4个字节,因为我这个编译器下int占据4个字节。有可能其他编译器64位程序中int 占据8个字节
传参的情况下(32位)
未完待续。。。