C++ 子函数参数传递过程
编译环境:Visual Studio 2015
参数传递与汇编语言有很大关系。子函数传递参数主要方式有三种(这三种参数传递方式都可用用于x86汇编语言甚至其它汇编语言):
- 寄存器方式传递参数
- 存储器方式传递参数
- 堆栈方式传递参数
在C++编译时,编译器采用堆栈方式传递参数。
测试代码:
int add_num(int x, int y) { x++; y++; int sum = x + y; cout << "add_num->x:" << (int*)&x << endl; cout << "add_num->y:" << (int*)&y << endl; return sum; } int main() { int sum = 0; int x = 3; cout << "main->x:" << (int*)&x<< endl; int y = 4; cout << "main->y:" << (int*)&y << endl; sum = add_num(x, y); return 0; }
add_num()函数反汇编代码
int add_num(int x, int y) { 001620C0 push ebp 001620C1 mov ebp,esp 001620C3 sub esp,0CCh 001620C9 push ebx 001620CA push esi 001620CB push edi 001620CC lea edi,[ebp-0CCh] 001620D2 mov ecx,33h 001620D7 mov eax,0CCCCCCCCh 001620DC rep stos dword ptr es:[edi] x++; 001620DE mov eax,dword ptr [x] 001620E1 add eax,1 001620E4 mov dword ptr [x],eax y++; 001620E7 mov eax,dword ptr [y] 001620EA add eax,1 001620ED mov dword ptr [y],eax int sum = x + y; 001620F0 mov eax,dword ptr [x] 001620F3 add eax,dword ptr [y] 001620F6 mov dword ptr [sum],eax cout << "add_num->x:" << (int*)&x << endl; 001620F9 mov esi,esp 001620FB push offset std::endl<char,std::char_traits<char> > (01614F1h) 00162100 mov edi,esp 00162102 lea eax,[x] 00162105 push eax 00162106 push offset string "add_num->x:" (0169B30h) 0016210B mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (016D098h)] 00162111 push ecx 00162112 call std::operator<<<std::char_traits<char> > (0161519h) 00162117 add esp,8 0016211A mov ecx,eax 0016211C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (016D0A4h)] 00162122 cmp edi,esp 00162124 call __RTC_CheckEsp (0161181h) 00162129 mov ecx,eax 0016212B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (016D0ACh)] 00162131 cmp esi,esp 00162133 call __RTC_CheckEsp (0161181h) cout << "add_num->y:" << (int*)&y << endl; 00162138 mov esi,esp 0016213A push offset std::endl<char,std::char_traits<char> > (01614F1h) 0016213F mov edi,esp 00162141 lea eax,[y] 00162144 push eax 00162145 push offset string "add_num->y:" (0169B3Ch) 0016214A mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (016D098h)] 00162150 push ecx 00162151 call std::operator<<<std::char_traits<char> > (0161519h) 00162156 add esp,8 00162159 mov ecx,eax 0016215B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (016D0A4h)] 00162161 cmp edi,esp 00162163 call __RTC_CheckEsp (0161181h) 00162168 mov ecx,eax 0016216A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (016D0ACh)] 00162170 cmp esi,esp 00162172 call __RTC_CheckEsp (0161181h) return sum; 00162177 mov eax,dword ptr [sum] //通过eax寄存器返回计算结果 } 0016217A pop edi 0016217B pop esi 0016217C pop ebx 0016217D add esp,0CCh 00162183 cmp ebp,esp 00162185 call __RTC_CheckEsp (0161181h) 0016218A mov esp,ebp 0016218C pop ebp 0016218D ret
在函数add_num内部,x地址为0x00f8fc4c,y地址为0x00f8fc50,函数ebp地址为0x00f8fc44。
main函数反汇编代码:
int main() { 00162270 push ebp 00162271 mov ebp,esp //保存main函数基地址0x00f8fd48 00162273 sub esp,0E8h //申请栈内存空间,栈顶地址为0x00f8fc60 00162279 push ebx 0016227A push esi 0016227B push edi 0016227C lea edi,[ebp-0E8h] 00162282 mov ecx,3Ah 00162287 mov eax,0CCCCCCCCh 0016228C rep stos dword ptr es:[edi] 0016228E mov eax,dword ptr [__security_cookie (016C004h)] 00162293 xor eax,ebp 00162295 mov dword ptr [ebp-4],eax//计算sum变量地址=ebp-4字节(前面有4次压栈)-4字节(默认变量间隔)-4字节(变量本身长度)=0x00f8fd48-12=0x00f8fd3c。 int sum = 0; 00162298 mov dword ptr [sum],0 int x = 3; 0016229F mov dword ptr [x],3 //同理 x地址= sum地址-8字节(默认变量将)-4字节(变量本身长度)=0x00f8fd30。 cout << "main->x:" << (int*)&x<< endl; 001622A6 mov esi,esp 001622A8 push offset std::endl<char,std::char_traits<char> > (01614F1h) 001622AD mov edi,esp 001622AF lea eax,[x] 001622B2 push eax 001622B3 push offset string "main->x:" (0169B50h) 001622B8 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (016D098h)] 001622BE push ecx 001622BF call std::operator<<<std::char_traits<char> > (0161519h) 001622C4 add esp,8 001622C7 mov ecx,eax 001622C9 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (016D0A4h)] 001622CF cmp edi,esp 001622D1 call __RTC_CheckEsp (0161181h) 001622D6 mov ecx,eax 001622D8 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (016D0ACh)] 001622DE cmp esi,esp 001622E0 call __RTC_CheckEsp (0161181h) int y = 4; 001622E5 mov dword ptr [y],4 //同理,y地址= x地址-8字节-4字节 = 0x00f8fd24。sum ,x ,y地址在下图显示结果一致。 cout << "main->y:" << (int*)&y << endl; 001622EC mov esi,esp 001622EE push offset std::endl<char,std::char_traits<char> > (01614F1h) 001622F3 mov edi,esp 001622F5 lea eax,[y] 001622F8 push eax 001622F9 push offset string "main->y:" (0169B5Ch) 001622FE mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (016D098h)] 00162304 push ecx 00162305 call std::operator<<<std::char_traits<char> > (0161519h) 0016230A add esp,8 0016230D mov ecx,eax 0016230F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (016D0A4h)] 00162315 cmp edi,esp 00162317 call __RTC_CheckEsp (0161181h) 0016231C mov ecx,eax 0016231E call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (016D0ACh)] 00162324 cmp esi,esp 00162326 call __RTC_CheckEsp (0161181h) sum = add_num(x, y); 0016232B mov eax,dword ptr [y] 0016232E push eax //形参y压栈 0016232F mov ecx,dword ptr [x] 00162332 push ecx //形参x压栈 00162333 call add_num (01614E7h) 00162338 add esp,8 0016233B mov dword ptr [sum],eax //将add_num函数存放在eax寄存器的计算结果赋给sum变量。 return 0; 0016233E xor eax,eax }
main()函数ebp地址为0x00f8fd48。x地址为0x00f8fd30,且x地址中放的还是调用add_num函数之前的原始值3。y地址为0x00f8fd24,且x地址中放的还是调用add_num函数之前的原始值4。sum地址为0x00f8fd3c,该地址存放的值在调用add_num函数之后由0变为9。
流程分析:
- 变量 x,y 存放在main函数栈中。
- 调用子函数之前,将变量x,y的值在main函数栈顶压栈(参数栈)。
- 进入子函数后,将将当前栈顶地址作为子函数基地址,其中保存调用函数的基地址。
- 子函数申请内存空间。并在参数栈中执行x++,y++运行,通过寄存器eax计算x+y结果,在子函数栈内计算sum地址,并保存运行结果。
- 最后通过 eax寄存器返回子函数返回值。
结论:
- C++编译器采用堆栈方式传递参数。
- 子函数对参数的运算在参数栈上进行。在参数栈上的运算不会影响到变量x,y变量地址中存放的值。
内存运行示意图: