本文基於win32下彙編。
1. 函數參數
函數棧增長方向與地址方向相反,從高地址向低地址增長。esp指向函數棧頂,ebp指向函數棧底。
sub esp XXX 將XXX長度的內存塊壓入棧
add esp XXX將XXX長度的內存塊彈出棧
參數壓棧順序示例
int param1 = 1;
00C939DF mov dword ptr [param1],1
int param2 = 3;
00C939E6 mov dword ptr [param2],3
int sum = pushParametersOrderApp(param1, param2); # 壓棧順序跟參數申明順序相反!
00C939F0 mov eax,dword ptr [param2] # 將param2值放入eax
00C939F6 push eax # 壓棧eax
00C939F7 mov ecx,dword ptr [param1] # 將param1值放入ecx
00C939FA push ecx # 壓棧ecx
00C939FB call pushParametersOrderApp (0C911F9h) # 函數調用
00C93A00 add esp,8 # 將之前壓棧的eax和ecx兩個值移除。pop操作是將esp加8,說明棧增長方向是從高地址到低地址。
00C93A03 mov dword ptr [sum],eax # 函數返回值在eax中,存入sum變量中
# 函數調用現場
int pushParametersOrderApp(int param1, int param2)
{
00E41F80 push ebp # 保存之前ebp指針,esp += 4
00E41F81 mov ebp,esp # 將新的esp賦給ebp指向當前函數棧底
00E41F83 sub esp,0D8h # 當前函數棧幀 預留216(0D8)的字節,存放自動變量
00E41F89 push ebx
00E41F8A push esi
00E41F8B push edi
00E41F8C lea edi,[ebp-0D8h] # 棧頂指針地址存入edi
00E41F92 mov ecx,36h # 棧上54(36h)個整數,即216(0D8)個字節
00E41F97 mov eax,0CCCCCCCCh # 初始化的數值
00E41F9C rep stos dword ptr es:[edi] # 初始化棧空間
int temp1 = param1;
00E41F9E mov eax,dword ptr [param1] # param1的值存入eax
00E41FA1 mov dword ptr [temp1],eax # eax值存入temp1中
int temp2 = param2;
00E41FA4 mov eax,dword ptr [param2] # 同理
00E41FA7 mov dword ptr [temp2],eax
return temp1 + temp2;
00E41FAA mov eax,dword ptr [temp1] # temp1值存入eax
00E41FAD add eax,dword ptr [temp2] # 相加結果存入eax,與函數返回後00C93A03行操作對應,函數結果在eax中
}
00FE1FB0 pop edi
00FE1FB1 pop esi
00FE1FB2 pop ebx
00FE1FB3 mov esp,ebp # 還原esp
00FE1FB5 pop ebp # 還原ebp,同時esp-=4
00FE1FB6 ret
pushParametersOrderApp函數調用前後堆棧:
上圖棧從下往上增長。代碼比較簡單,對兩個變量進行求和,結果返回。通過彙編源碼說明幾點:
1)C/C++ 中函數參數壓棧順序:與申明順序相反
2)函數棧幀的定義:esp和ebp之間的內存塊
3)棧上自動變量的初始化,使用0CCCCCCCC
4)函數參數在函數棧幀的外面,見00C939F6、00C939FA行;函數局部變量參數定義在棧上。
傳值拷貝
struct TestItem
{
int a;
int b;
int arr[10];
};
int passValueApp(TestItem item)
{
int value = item.a;
int value1 = item.b;
return value;
}
int passReferenceApp(TestItem& item)
{
int value = item.a;
int valu1 = item.b;
return value;
}
int passPointerApp(TestItem* item)
{
int value = item->a;
int value1 = item->b;
return value;
}
TestItem item = {1, 4, {1,2,3}};
00FE3948 mov dword ptr [item],1 # &item.a==item,a置爲1
00FE394F mov dword ptr [ebp-30h],4 # ebp-48 通過ebp指針訪問b
00FE3956 mov dword ptr [ebp-2Ch],1 # ebp-44
00FE395D mov dword ptr [ebp-28h],2 # ebp-40
00FE3964 mov dword ptr [ebp-24h],3 # ebp-36
00FE396B xor eax,eax # 異或操作將eax置零
00FE396D mov dword ptr [ebp-20h],eax # ebp-32
00FE3970 mov dword ptr [ebp-1Ch],eax # ebp-28
00FE3973 mov dword ptr [ebp-18h],eax # ebp-24
00FE3976 mov dword ptr [ebp-14h],eax # ebp-20
00FE3979 mov dword ptr [ebp-10h],eax # ebp-16
00FE397C mov dword ptr [ebp-0Ch],eax # ebp-12
00FE397F mov dword ptr [ebp-8],eax # ebp-8
passValueApp(item); ###### 傳結構體函數 ######
00FE3982 sub esp,30h # 參數值拷貝,esp減去48(30h)正好sizeof TestItem大小
00FE3985 mov ecx,0Ch # 指定要拷貝次數,0Ch即12個整數,對用48byte
00FE398A lea esi,[item] # 獲取item地址
00FE398D mov edi,esp # 拷貝基地址指定
00FE398F rep movs dword ptr es:[edi],dword ptr [esi] # rep循環12次拷貝
00FE3991 call passValue (0FE11E5h) #
00FE3996 add esp,30h # 將參數拷貝彈出棧空間,esp+=48
passPointerApp(&item); ###### 傳指針函數 ######
00FE3999 lea eax,[item] # 獲取item地址存入eax
00FE399C push eax # 參數值拷貝,壓棧eax esp-=4
00FE399D call passPointerApp (0FE11EFh) #
00FE39A2 add esp,4 # 將參數拷貝彈出棧空間,esp+=4
passReferenceApp(item);
00FE39A5 lea eax,[item] # 彙編層面引用版本和指針一樣
00FE39A8 push eax
00FE39A9 call passReferenceApp (0FE11EAh)
00FE39AE add esp,4
該示例說明了函數傳值拷貝問題:
1)passValueApp函數調用需要在棧幀上預留48字節的空間,00FE398D、00FE398F拷貝item結構體,函數執行完成以後將esp加上48字節將參數拷貝出棧。
2)passPointerApp函數傳指針,00FE399C行將指針變量拷貝入棧,函數調用完成將esp+4完成參數拷貝出棧操作。
3)passReferenceApp 傳引用於傳指針原理一樣。
由此可見,大數據結構一定要傳指針或者引用。
2. 函數返回值
# 函數內部堆棧現場
TestItem returnStructApp()
{
00EC3850 push ebp
00EC3851 mov ebp,esp
00EC3853 sub esp,0F8h
00EC3859 push ebx
00EC385A push esi
00EC385B push edi
00EC385C lea edi,[ebp-0F8h]
00EC3862 mov ecx,3Eh
00EC3867 mov eax,0CCCCCCCCh
00EC386C rep stos dword ptr es:[edi]
TestItem item = { 1,2, {3,4,5} };
00EC386E mov dword ptr [item],1
00EC3875 mov dword ptr [ebp-30h],2
00EC387C mov dword ptr [ebp-2Ch],3
00EC3883 mov dword ptr [ebp-28h],4
00EC388A mov dword ptr [ebp-24h],5
00EC3891 xor eax,eax
00EC3893 mov dword ptr [ebp-20h],eax
00EC3896 mov dword ptr [ebp-1Ch],eax
00EC3899 mov dword ptr [ebp-18h],eax
00EC389C mov dword ptr [ebp-14h],eax
00EC389F mov dword ptr [ebp-10h],eax
00EC38A2 mov dword ptr [ebp-0Ch],eax
00EC38A5 mov dword ptr [ebp-8],eax
return item;
00EC38A8 mov ecx,0Ch # 拷貝長度12個整數
00EC38AD lea esi,[item] # 獲取item地址
00EC38B0 mov edi,dword ptr [ebp+8] # 臨時對象1地址
00EC38B3 rep movs dword ptr es:[edi],dword ptr [esi] # 將item內容拷貝到edi指向內存區,edi被改變
00EC38B5 mov eax,dword ptr [ebp+8] # 將臨時對象1地址存入eax寄存器,傳出函數!
}
00EC38B8 push edx
00EC38B9 mov ecx,ebp
00EC38BB push eax
00EC38BC lea edx,[ (0EC38D0h)]
00EC38C2 call @ILT+135(@_RTC_CheckStackVars@8) (0EC108Ch)
00EC38C7 pop eax
00EC38C8 pop edx
00EC38C9 pop edi
00EC38CA pop esi
00EC38CB pop ebx
00EC38CC mov esp,ebp
00EC38CE pop ebp
00EC38CF ret
# 函數調用堆棧現場
TestItem item1 = returnStructApp();
00EC39B1 lea eax,[ebp-188h]
00EC39B7 push eax # returnStructApp是個無參函數,但是也會壓eax入棧
00EC39B8 call returnStructApp (0EC11F4h)
00EC39BD add esp,4 # 彈出eax
00EC39C0 mov ecx,0Ch # 拷貝長度12整數
00EC39C5 mov esi,eax # eax指向函數返回的臨時對象數據區,在returnStructApp函數內部被修改
00EC39C7 lea edi,[ebp-1C0h] # 臨時對象2內存塊地址
00EC39CD rep movs dword ptr es:[edi],dword ptr [esi] # 循環執行數據拷貝,從函數內部臨時對象1到臨時對象2
00EC39CF mov ecx,0Ch # 拷貝長度12整數
00EC39D4 lea esi,[ebp-1C0h] # 臨時對象2內存塊地址
00EC39DA lea edi,[item1] # 目標item1數據區
00EC39DD rep movs dword ptr es:[edi],dword ptr [esi] # 循環執行數據拷貝,從臨時對象2到item1
上面代碼中,函數中返回一個結構體對象,然後函數返回值賦給item1,函數返回過程中構造了兩個臨時對象,一共執行了3次數據拷貝。所以效率比較低。儘量避免直接返回結構對象。
3. 參考
1. http://blog.csdn.net/ENO_REZ/article/details/2158682
2. http://blog.csdn.net/vagrxie/article/details/2501238