1 函数的堆栈
#include<stdio.h>
int sum(int a, int b)
{
/*
push ebp
mov ebp,esp//将ebp移到栈顶(做新栈内存的栈底)
sub esp,0CCh
push ebx //空间初始化
push esi
push edi
lea edi,[ebp-0CCh]
mov ecx,33h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
*/
int temp = 0;
//mov dword ptr[ebp-8], 0
temp = a + b;
//mov eax,dword ptr[ebp+8]
//add eax,dword ptr[ebp+0Ch]
//mov dword ptr[ebp - 8], eax
return temp;
//mov eax,dword ptr[ebp-8]
}
//mov esp,ebp 释放空间
//pop ebp 弹出ebp给栈底,即回归主调用 栈
//ret 弹出地址给PC指针,根据PC指针跳转
int main()
{
//开辟栈空间
/*
push ebp //将栈底ebp入栈
mov ebp,esp //将ebp移动到esp处
sub esp,0E4h //将栈顶esp向上移动 228字节(开辟228字节空间)
push ebx
push esi
push edi
lea edi,[ebp-0E4h]
mov ecx,39h //39次
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi] //初始化0CCCCCCCCh
*/
int a = 10;
//mov dword ptr[ebp-8],0Ah
int b = 20;
//mov dword ptr[ebp-14h],14h
int ret = 0;
//mov dword ptr[ebp-20h],0
ret = sum(a, b);
//mov eax,dword ptr[ebp-14h] //从右向左入栈
//push eax
//mov ecx,dword ptr[ebp-8]
//push ecx//被调动函数的形参参数是调动方开辟
// 如果返回值类型大于8字节,即此时主调方会产生一个临时量的地址 压入push
//call sum(04D1023h) 将PC指针压入栈,跳转到sum函数入口
//add esp,8 回收形参内存
//mov dword ptr[ebp-20h],eax
printf("ret=%d\n", ret);
return 0;
}
/*
函数的调用时之前,需要扫描函数的声明和定义,然后对实参列表进行解参(与形参个数是否匹配,然后类型是否匹配(是否可以类型转换))
当在一个函数的运行期间调用另一个函数时,在运行被调函数之前,系统需要完成三件事:
1、将所有的实际参数,返回地址等信息传递给被调函数。
2、为被调函数的局部变量(也包括形参)分配存储空间
3、将控制转移到被调函数的入口
从被调函数返回主调函数之前,系统也要完成三件事:
1、保存被调函数的返回结果
2、释放被调函数所占的存储空间(esp=ebp)
3、依照被调函数保存的返回地址将控制转移到调用函数
所以被调函数的形参、返回地址、栈底ebp的空间其实是由主调方开辟的内存(在主调方栈里开辟内存),只是在被调方结束时自己释放内存而形参的内存却是由主调方释放
返回的结果如果大于8字节时,在主调方会产生临时量内存,被压入栈中。
在被调函数里对形参的访问是通过栈底操作 ebp+8 ebp+0ch
Call 两个动作
1.压入PC所指向地址
2.call跳转到对应函数(此时地址是偏移量)
Ret 两个动作
1.出栈,将栈顶元素赋给PC寄存器
2.根据PC跳转到call后面的指令
2 函数的返回值
临时量可能产生的阶段:
1.函数调用之前产生临时量。(接收返回值)
2.函数调用,在return 产生临时量
3.函数调用完成之后产生临时量 (const int &a)
内置类型返回的是常量,不可修改(右值,不可寻址),要想返回变量加可以被&返回的变量
自定义类型产生的临时量都是变量,可以修改
ps:返回的临时量都是放在一个寄存器里,只不过可能是数值,也可能是地址,需要进行两次拷贝。
(一次返回时将其拷贝给临时量,第二次将临时量的值拷贝给函数表达式)
3 函数调用约定
_cdecl C调用约定(默认)
_stdcall windows标准的调用约定
_fastcall 快速调用约定
_thiscall C++的成员函数的调用约定
_pascal (已弃用)
调用方式影响函数的三个方面
- 函数产生的符号名字不同
- 函数参数入栈顺序不同
右->左 - 谁来清理形参的内存(主要看的还是谁来清理参数)
int _cdecl sum(int a,int b);
//file1.cpp内声明 其.obj产生的符号sum在*UND*,对sum的引用
int _stdcall sum(int a,int b)
{
return a+b;
}
//file2.cpp内定义 其.obj产生的符号sum在*.text*
ps:
函数声明时会产生符号, 在UND区 —所有的符号引用都要到定位到对符号的定义(在链接时需要进行符号解析)
由于不同的调用方式产生的符号不同(编译器认为其就是不同的符号),无法进行符号解析
_cdecl 调用方开辟形参内存,调用方释放形参内存(主调方eps+8)
_stdcall 调用方开辟形参内存,被调用方自己释放形参内存(被调方ret 8 )
//ret 8
1.将地址弹出放入PC
2.系统自动回退8个字节地址(形参)
_fastcall 把最左边(最后入栈)(也就是最后8字节的实参通过寄存器带到被调用函数中)
若当参数大于8byte则需要入栈操作(调用方开辟形参内存),但也是把最左边(也就是最后8字节的实参通过寄存器带到被调用函数中),被调用方自己释放形参内存
PS其函数实参通过寄存器传给形参而不需要进行入栈,出栈