閒着沒事想研究一下gcc的函數調用方式和m$的__stdcall、__fastcall之類有何區別,本想是瞭解一下關於參數的入棧順序和清理方,就隨便寫了個C函數,編譯成.s文件,一看發現根本就沒有push和pop之類的指令...兩個int參數都是利用rsi和rdi傳遞!網上百度了一個關於m$平臺x64的調用約定linux
看完ddk裏相關的部分,總結下吧,規則卻是不復雜,相對x86時代的stdcall cdecl fastcall 三分天下要簡明的多。按ddk裏的說法,m$就是要趁此次統一調用規則…… -__-算法
在x64下函數調用的前4個參數老是放在寄存器中傳遞,剩餘的參數則壓入堆棧中。而x86上則是所有壓入堆棧中(除了fastcall方式)。這4個用於存放參數的寄存器分別是:存放整數參數的RCX,RDX,R8,R9;存放浮點數參數的XMM0,XMM1,XMM2,XMM3。函數
按照所傳參數是整數仍是浮點數的不一樣,寄存器的使用規則以下:優化
所有整數參數:指針
func1(int a, int b, int c, int d, int e);編譯器
參數a放入RCX,參數b放入RDC,參數c放入R8,參數d放入R9,參數e麼壓棧。數學
參數傳遞規則:按照參數表聲明的順序,從左向右,前4個參數依次放入RCX,RDX,R8,R9中。it
所有浮點數參數:編譯
func1(float a, float b, float c, double d, float e);ast
a放入XMM0,b放入XMM1,c放入XMM2,d放入XMM3,e壓棧
參數傳遞規則:按照參數聲明的順序,從左向右,前4個參數依次放入XMM0,XMM1,XMM2,XMM3中
整數和浮點數參數混合出現:
func3(float a, int b, double c, int d)
a放入XMM0中,b放入RDX,c放入XMM2,d放入R9。
這裏比較特殊,其實就是按照這個規則:
a b c d
RDX R9
XMM0 XMM2
也就是說4個整數寄存器嚴格的一一對應前4個參數,一樣前4個XMM寄存器嚴格的一一應前4個參數,若是是整數浮點數間隔出現,那麼就保持對應關係,選擇對應的寄存器便可。
指針參數:
指針的傳遞遵循整數參數傳遞方式。
結構體參數:
結構體特殊一點,按照ddk的描述,若是結構體長度小於64bit,則使用整數參數的傳遞規則。但若是是一個很大的結構體,那麼應該仍是要在堆棧中申請臨時空間的(但ddk沒有明說這一點,參考x86的規則應該如此)。
未聲明函數的調用:
ddk裏特別列舉了這樣一個例子:
func1();
func2(){
func1(2, 1.0, 7)
}
在這種狀況下,func1()的參數表其實不明確,那麼參數的傳遞要怎樣進行?這裏採用了一個比較保守的規則,就是:整數參數仍是按照寄存器映射關係放入對應的寄存器中,浮點數在按照映射關係放入XMM寄存器後,還須要按照整數參數的寄存器映射關係放入整數寄存器中一次,這就是爲啥我說是「比較保守的規則」。就如今這個例子而言,結果以下:
2在RCX中,1.0在RDX和XMM1中,7在R8中。
然而gcc的用法卻與此大相徑庭:
版權爲 win_hate 全部, 轉載請保留做者名字
我這段時間要把之前的一個 x86_32 的 linux 程序移植到 x86_64(AMD) 的 linux 環境裏. 因爲寫的是數學算法, 64 與 32 位有很大不一樣, 代碼實際上要重寫. 看了點資料後, 以爲 AMD64 的擴展於之前 16 到 32 位的擴展很相似, e**, 擴展爲 r**, 此外還多了8個通用寄存器 r8~r15.指令格式與32位的極爲類似. 我以爲比較容易, 因此沒再仔細看, 就開始動手寫了.
個人程序由若干個彙編模塊於與若干個c模塊構成, 不少c模塊要調用匯編模塊. 做爲試驗, 我先寫了個簡單的彙編函數, 而後用c來調用. 結果算出來的值始終是錯誤的. 這令我很惱火, 由於函數很簡單, 沒有多少出錯的餘地. 後來我把程序反彙編出來, 錯誤立刻浮現出來了, 函數的參數竟然是經過寄存器來傳遞的. 我憑之前的經驗, 從堆棧裏取參數, 算出的結果固然不對了. 我之前不是沒碰到過用寄存器傳遞參數的狀況, 但所在的環境都不是 pc. 在 x86_32/linux 中, 即便用 -O3 優化選項, gcc 仍經過棧來傳遞參數的.
因此咱們如今知道, 在 x86_64/linux/gcc3.2 中, 即便不打開優化選項, 函數的參數也會經過寄存器來傳遞, 這確定是闊了的表現(通用寄存器多了).
我試驗了多個參數的狀況,發現通常規則爲, 當參數少於7個時, 參數從左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。當參數爲 7 個以上時, 前 6 個與前面同樣, 但後面的依次從 "右向左" 放入棧中。
例如:
CODE
(1) 參數個數少於7個:
f (a, b, c, d, e, f);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9
g (a, b)
a->%rdi, b->%rsi
有趣的是, 實際上將參數放入寄存器的語句是從右到左處理參數表的, 這點與32位的時候一致.
CODE
2) 參數個數大於 7 個的時候
H(a, b, c, d, e, f, g);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%rax
g->8(%esp)
f->(%esp)
call H
易失寄存器:
%rax, %rcx, %rdx, %rsi, %rdi, %r8, %r9 爲易失寄存器, 被調用者沒必要恢復它們的值。
顯然,這裏出現的寄存器大多用於參數傳遞了, 值被改掉也無妨。而 %rax, %rdx 經常使用於
數值計算, %rcx 經常使用於循環計數,它們的值是常常改變的。其它的寄存器爲非易失的,也
就是 rbp, rbx, rsp, r10~r15 的值若是在彙編模塊中被改變了,在退出該模塊時,必須將
其恢復。
教訓:
用匯編寫模塊, 而後與 c 整合, 必定要搞清楚編譯器的行爲, 特別是參數傳遞的方式. 此外, 我如今比較擔憂的一點是, 未來若是要把程序移植到 WIN/VC 環境怎麼辦? 之前我用cygwin的gcc來處理彙編模塊, 用vc來處理c模塊, 只須要不多改動. 如今的問題是, 若是VC用不一樣的參數傳遞方式, 那我不就麻煩了?
補充:
前面的參數 a, b, c, d 等, 都是整數, 長整數, 或指針, 也就是說, 能放到寄存器裏頭的. 若是你要傳遞一個很大的結構, 我估計編譯器也只能經過棧來傳遞了.
環境爲 AMD Athlon64, Mandrak linux 9.2, GCC3.3.1
對比上下兩篇文章可知,gcc和vc在x64的函數調用方式徹底不一樣,gcc和vc編譯的模塊想要互相調用貌似不太可能,除非其中的一種推出兼容模式