看板 C_and_CPP 關於我們 聯絡資訊
先回答你的 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 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
descent:感謝, 我想通了, 以補充在原 blog 最下方。 11/19 13:29