1. 程式人生 > >X64的函式呼叫規則

X64的函式呼叫規則

版權為 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