推 descent:感謝, 我想通了, 以補充在原 blog 最下方。 11/19 13:29
see?' Hermione breathed. 'If she could have done
one thing to make absolutely sure that every single person in this school
will read your interview, it was banning it!'
---'Harry Potter and the order of the phoenix', P513
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 180.218.108.125
先回答你的 Q2 和 Q3
可變參數跟哪一邊清堆疊這兩件事是有關連的
所謂可變參數就是 callee 可能不會知道傳進來的參數有多少個
因此既然 callee 不知道 只好讓知道的 caller 來清堆疊
最簡單的例子就是 printf
我們在使用 printf 時會丟一堆有的沒有的參數進去
那 compiler 不可能在編譯 printf 這支函式的時候就知道以後誰會丟什麼參數進來
所以只好把清除堆疊的工作交給呼叫 printf 的函式 (caller)
相對的 不支援可變參數表示所有函式的參數個數都是固定的
也就是說 callee 會明確的知道自己有哪些參數傳進來
因此 callee 就能夠在結束時幫忙把參數給清除
在 Win 系統上面的 Win32 API 所使用的 stdcall 就是 callee 清除堆疊
因為這些 API 的參數都是固定的 沒有必要為了這些東西增加程式碼長度
(以 x86 assembly 來說
callee 清除堆疊只需要在 ret 指令加上一個 16-bit 常數指定清除的位元組數
但 caller 清除堆疊則通常需要額外的一個 add $xx,%esp 的加法指令)
這兩件事跟左到右還是右到左都沒什麼相關
這個方向只是一個約定而已
只不過兩邊得要一致才不致於搞錯到底哪個參數是哪個 (就像你的 foo4 一樣)
那現在常用的 calling convention 大多數都是右到左而已
另外你在 Q3 多問的那個問題
你的問題應該是卡在編譯器看似是知道你這一趟傳了 3 個參數進去
但其實不是這樣的
可變參數代表我要扔幾個參數進去都可以
那也許我這一次呼叫 foo4(1, 2, 3);
下一次我呼叫 foo4(5, "Hello World!", 0.12345, NULL);
等等的
這些參數的個數跟型態都不固定 (除了有指定的前幾個以外)
那 foo4 這個函數自己要如何才能知道第一個在哪裡?
前一種情形也許要加上 $0xc 後一種情形可能要加上 $0x14 或等等的
在編譯 foo4 這個函數時是不可能知道這回事的
在由左而右的順序裡要加多少只有在一個狀況可以完全確定
就是這個函數的參數個數跟型態都完全固定的時候
因此像你的 foo2 才能夠用 mov 0x8(%esp),%eax 找到第一個參數
這也順帶說明了為什麼現在大多都是採由右而左的順序
特別是支援可變參數的一定會採用右到左
理由就是由右到左推入堆疊的話 第一個參數會在堆疊的高處 = 接近堆疊指標處
那麼第一個參數的位置就一定是堆疊指標往下數固定大小
不論有沒有可變參數這件事都是對的
而在有可變參數的情況下 第一個參數的位置就會是固定的
因此也能夠方便地由這個參數的位置去定位出其他的參數
(這正是 va_begin() va_arg() va_end() 這幾個 macro 在做的事)
再來是 Q1
其實我從來沒聽說過 pascal convention 效率比較好
你聽到的說法可能是程式碼會少一個指令之類的
但是那個指令只不過是我上面所提到的 add $xx,%esp 而已
--
'Oh, Harry, don't you