看看如下代码:
int sum(int a,int b)
{
int temp = 0;
temp = a + b;
return temp;
}
int main()
{
int a = 10;
int b = 10;
int ret = sum(a,b);
std::cout<< "ret :"<<ret<<std::endl;
return 0;
}
看着上面的函数调用代码片段,我们有几个问题:
1.main函数调用sum,sum执行完毕后,怎么知道回到那个函数?
2.sum函数执行完后,回到main以后,怎么知道从哪一行代码开始执行。
函数在运行的时候会在栈上开辟空间,函数调用时候参数压栈是从右向左。call指令首先会把下一行指令入栈,每个函数的左括号和右括号都是会产生指令的。最后返回会把下一个要执行的指令放到PC寄存器里面。
nt a = 10;
008319C5 mov dword ptr [a],0Ah
int b = 20;
008319CC mov dword ptr [b],14h
int ret = sum(a, b);
008319D3 mov eax,dword ptr [b]
008319D6 push eax
008319D7 mov ecx,dword ptr [a]
008319DA push ecx
008319DB call sum (083118Bh)
008319E0 add esp,8
008319E3 mov dword ptr [ret],eax
std::cout << ret << std::endl;
008319E6 mov esi,esp
int sum(int a, int b) {
00831940 push ebp
00831941 mov ebp,esp
00831943 sub esp,0CCh
00831949 push ebx
0083194A push esi
0083194B push edi
0083194C lea edi,[ebp-0Ch]
0083194F mov ecx,3
00831954 mov eax,0CCCCCCCCh
00831959 rep stos dword ptr es:[edi]
0083195B mov ecx,offset _FF03E44B_class1_1@cpp (083C0E1h)
00831960 call @__CheckForDebuggerJustMyCode@4 (083132Fh)
int temp = 0;
00831965 mov dword ptr [temp],0
temp = a + b;
0083196C mov eax,dword ptr [a]
0083196F add eax,dword ptr [b]
00831972 mov dword ptr [temp],eax
return temp;
00831975 mov eax,dword ptr [temp]
}
再复习一下32位操作系统下面进程的虚拟地址空间布局:0-3G是进程私有的用户空间,3-4G是进程共享的内核空间。
函数的调用惯例:函数的调用方和被调用方对于函数如何调用必须要有一个明确的约定,只有双方都遵守这个约定,函数才能正确的被调用。
- 函数参数的传递方式:最常见的函数参数传递是通过栈传递,函数的调用方将参数压入栈中,函数自己再从栈中将数据取出来。对于有多个参数的函数,参数压栈的顺序是从右到左。
- 名字修饰(Name-mangling)的策略:为了连接的时候对调用惯例进行区分,需要对函数本身的名字进行修饰,不同的调用惯例有不同的名字修饰策略;
默认的调用惯例是:函数的参数传递是从右到左,在C语言中名字修饰是在函数名前面加上_下划线,而C++对函数进行名字修饰是则不一样,更为复杂;
- 函数返回值传递:小于等于4字节的返回值通常是通过eax寄存器返回;在(4,8]之间的函数返回值通过两个寄存器eax和edx存储后返回;
大于8字节的,利用临时对象进行返回值的传递;
1、首先在mian函数中的栈上开辟一片额外的空间作为临时对象
2、调用函数时将该临时对象的地址通过隐藏的参数传递给函数
3、函数内部将返回值的数据拷贝到临时对象,并将临时对象的地址通过寄存器eax传出。
4函数返回后,将eax指向的临时对象的内存拷贝到main函数中的变量
参考书籍:《程序员的自我修养-装载链接与库》