1. 程式人生 > >C#中的函數(二) 有參有返回值的函數

C#中的函數(二) 有參有返回值的函數

lis function AD 編程 all bsp slist title 多余

接上一篇 C#中的函數(-) 無參無返回值的函數

http://www.cnblogs.com/fzxiaoyi/p/8502613.html

這次研究下C#中的函數(二) 有參有返回值的函數

依然寫一個小例子,用來測試

跟上一個例子差不多,區別就是MyFunction有二個參數a,b,返回二個數相加的值

技術分享圖片

F5調試運行,中斷後轉到反匯編

這裏很明顯看到不同了

這裏就得講到參數傳遞的方式,參數從左向右依次存入寄存器ecx edx

但是不同的編程語言有不同的傳遞參數的方式,有空再寫一篇文章介紹下

技術分享圖片

要是學過匯編的,到這裏又有疑問了,要是每個實參都存儲在寄存器中,那超過寄存器數量怎麽辦?

有疑惑就得測試,這是一貫原則,好吧再開個工程,寫一個3個參數的相同函數

技術分享圖片

轉到反匯編,Look一下, 第三個實參是直接壓棧

技術分享圖片

那如果五個參數呢,再寫一個函數測試下

技術分享圖片

到這就很明了了

總結一下: 如果傳遞的實參數量小於2個使用的ECX EDX寄存器,如果大於2個參數,

使用堆棧進行傳遞.

OK,明白了參數傳遞的方式回到最開始的例子,進一步分析MyFunction函數內部

F10單步執行二次,停在001F2DD5

技術分享圖片

然後F11進入函數內部查看

這裏反匯編代碼太長,就不截圖了,直接貼反匯編代碼

001F2DF8 push ebp //前面這二句用來保存esp
001F2DF9 mov ebp,esp //此時ebp指向棧頂
001F2DFB push edi


001F2DFC push esi
001F2DFD push ebx //對edi esi ebx寄存器壓棧進行保存

//這裏開始就是我們研究的重點了,可以F10單步執行,順帶查看寄存器跟堆棧中值

//上一篇中這裏是sub esp,2ch,而這裏是3ch

//這裏多出來的10h是返回值地址,二個形參地址,一個臨時變量sum地址

001F2DFE sub esp,3Ch //接合上面那三句,此時esp移動了0xC + 0x3C = 0x48個字節

001F2E01 mov esi,ecx //這個ecx 是1, 代表第一個實參,保存在esi中

001F2E03 lea edi,[ebp-38h] // 取地址操作esp + 0x10 = 從棧頂往下第四個位置


001F2E06 mov ecx,0Bh //這三行把0xB* 4 = 44個字節空間清零
001F2E0B xor eax,eax
001F2E0D rep stos dword ptr es:[edi]
001F2E0F mov ecx,esi //把參數1保存在ecx
001F2E11 mov dword ptr [ebp-3Ch],ecx // 把參數1保存在[ebp-0x3C] 處
001F2E14 mov dword ptr [ebp-40h],edx //把參數2保存在[ebp-0x40h]處
001F2E17 cmp dword ptr ds:[0018C7A8h],0
001F2E1E je 001F2E25
001F2E20 call 71C6CB2D
001F2E25 xor edx,edx
001F2E27 mov dword ptr [ebp-48h],edx
001F2E2A xor edx,edx
001F2E2C mov dword ptr [ebp-44h],edx
001F2E2F nop

//這裏就是具體計算的地方
22: int sum = a + b;

001F2E30 mov eax,dword ptr [ebp-3Ch] //把第一個參數存儲在eax中
001F2E33 add eax,dword ptr [ebp-40h] //然後通過add把eax + 第二個參數,結果就是1 + 2 = 3
001F2E36 mov dword ptr [ebp-44h],eax //然後把相加的結果放在臨時變量中
001F2E39 mov eax,dword ptr [ebp-44h] //又從臨時變量中取出結果放在eax(這句完全多余)
001F2E3C mov dword ptr [ebp-48h],eax //把結果放在返回值地址中. 有返回值情況下eax存儲的都是返回值

//這是當前堆棧中的情況

技術分享圖片

//這後面的代碼是當前函數返回時必須要做的堆棧平衡處理

001F2E3F nop
001F2E40 jmp 001F2E42
001F2E42 mov eax,dword ptr [ebp-48h]
001F2E45 lea esp,[ebp-0Ch]
001F2E48 pop ebx
001F2E49 pop esi
001F2E4A pop edi
001F2E4B pop ebp
001F2E4C ret

多於二個參數時,也順帶看一下核心代碼

前面說過,多余二個參數時,前二個參數存儲在寄存器ECX EDX中,後面的參數存儲在堆棧中

18: int sum = a + b + c + d;
001A34B0 mov eax,dword ptr [ebp-3Ch] //eax = 第一個參數
001A34B3 add eax,dword ptr [ebp-40h] //eax = eax + 第二個參數
001A34B6 add eax,dword ptr [ebp+0Ch] //eax += 第三個參數 直接從堆棧中取不需要再開辟棧空間
001A34B9 add eax,dword ptr [ebp+8] //eax += 第四個參數 直接從堆棧中取不需要再開辟棧空間
001A34BC mov dword ptr [ebp-44h],eax // 臨時變量sum = 前四個數之和
19: return sum;
001A34BF mov eax,dword ptr [ebp-44h] //把臨時變量sum給eax
001A34C2 mov dword ptr [ebp-48h],eax //把結果放在返回值地址中. 有返回值情況下eax存儲的都是返回值
001A34C5 nop
001A34C6 jmp 001A34C8

C#中的函數(二) 有參有返回值的函數