這一文章中我們主要分析VS平台對於函數調用的編譯處理,首先我們看一個簡單的例子,代碼如下:
void Hello(){ } int main(){ Hello(); }
然後在VS Command Prompt下面用cl -FA main.cpp 編譯一下,你會得到一個彙編的檔案main.asm,其內容如下:
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 TITLE E:\bossjue\main.cpp .686P .XMM include listing.inc .model flatINCLUDELIB LIBCMTINCLUDELIB OLDNAMESPUBLIC ?Hello@@YAXXZ ; Hello; Function compile flags: /Odtp_TEXT SEGMENT?Hello@@YAXXZ PROC ; Hello; File e:\bossjue\main.cpp; Line 1 push ebp mov ebp, esp; Line 2 pop ebp ret 0?Hello@@YAXXZ ENDP ; Hello_TEXT ENDSPUBLIC _main; Function compile flags: /Odtp_TEXT SEGMENT_main PROC; Line 4 push ebp mov ebp, esp; Line 5 call ?Hello@@YAXXZ ; Hello; Line 6 xor eax, eaxpop ebp ret 0_main ENDP_TEXT ENDSEND
別怕,有我給你們解釋這些代碼,這些代碼可以說其實都不用看,只需要看有標記;Line 1、;Line 2……這些行就行了,首先在Lin 4,這是main函數的開始,裡面首先儲存了棧幀,然後Lin 5那裡跳到Hello函數裡面去執行Hello裡面的代碼。也就是說,如果我們寫以下的代碼也應該是對的。
void Hello(){}int main(){ __asm{ call Hello }}
可以編譯,看到程式正常運行,說明我們的call調用是成功的,在此說明了一點,函數調用最後關鍵的一步是call調用。
上面只是簡單的一個Hello函數,下面我們用難一點的去測試,我們寫如下代碼:
int Hello(){ return 100;}int main(){ Hello();}
這個代碼和上面代碼的區別在於這個函數Hello 有傳回值,同樣地用cl -FA main.cpp編譯得到main.asm,開啟main.asm看到其內容如下:
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 TITLE E:\bossjue\main.cpp .686P .XMM include listing.inc .model flatINCLUDELIB LIBCMTINCLUDELIB OLDNAMESPUBLIC ?Hello@@YAHXZ ; Hello; Function compile flags: /Odtp_TEXT SEGMENT?Hello@@YAHXZ PROC ; Hello; File e:\bossjue\main.cpp; Line 1 push ebp mov ebp, esp; Line 2 mov eax, 100 ; 00000064H; Line 3 pop ebp ret 0?Hello@@YAHXZ ENDP ; Hello_TEXT ENDSPUBLIC _main; Function compile flags: /Odtp_TEXT SEGMENT_main PROC; Line 5 push ebp mov ebp, esp; Line 6 call ?Hello@@YAHXZ ; Hello; Line 7 xor eax, eax pop ebp ret 0_main ENDP_TEXT ENDSEND
在Line2裡面可以看到有一句mov eax, 100,聯想到Hello裡面返回100,可以猜測VS裡面是用eax傳遞傳回值的,其實確實是這樣的,可以用下面的代碼測試:
#include <iostream>using namespace std;int Hello(){ __asm{ mov eax, 100 }}int main(){ cout<<Hello()<<endl;}
可以看到輸出100,說明我了們的結果是對的,函數的傳回值是存放在eax進而的。
下面我們再給Hello傳遞參數試試,來進一步說明參數傳遞的原理代碼如下
void Hello(int iVA, int iVB){}int main(){ Hello(3, 5);}
同樣的得到彙編的代碼如下:
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 TITLE E:\bossjue\main.cpp .686P .XMM include listing.inc .model flatINCLUDELIB LIBCMTINCLUDELIB OLDNAMESPUBLIC ?Hello@@YAXHH@Z ; Hello; Function compile flags: /Odtp_TEXT SEGMENT_iVA$ = 8 ; size = 4_iVB$ = 12 ; size = 4?Hello@@YAXHH@Z PROC ; Hello; File e:\bossjue\main.cpp; Line 1 push ebp mov ebp, esp; Line 3 pop ebp ret 0?Hello@@YAXHH@Z ENDP ; Hello_TEXT ENDSPUBLIC _main; Function compile flags: /Odtp_TEXT SEGMENT_main PROC; Line 5 push ebp mov ebp, esp; Line 6 push 5 push 3 call ?Hello@@YAXHH@Z ; Hello add esp, 8; Line 7 xor eax, eax pop ebp ret 0_main ENDP_TEXT ENDSEND
可以看到,其實不過是把參數用push傳遞進了stack裡面,然後就是一個call而已,當然是主調函數清除stack(一句add esp而已)
為了測試,我們寫下如下的代碼
#include <stdio.h>int main(){ char *lpstrFormat = "%s %d"; char *lpStr = "Hello, this year is: "; __asm{ push 2012 push lpStr push lpstrFormat call printf add esp, 12 }}
然後在命令列裡面運行得到了你想要的結果,呵呵。
當然,參數怎麼傳遞的和誰清除stack這個由調用方式決定的,調用方式一般有三種__stdcall, __cdecl, __fastcall,讀都可以在網上baidu一下,或者過幾天我自己寫文章說明。
到此,一般函數調用的過程已經完全說完了,如果想知道class的成員函數的調用過程,我在下一文章中說明。