blog 文章

2012年11月19日 星期一

pascal call convention in c - 3 questions

test env:
  • in ubuntu 10.04 64bit, but the test code is 32bit
  • intel x86

gcc 無法提供 pascal call convention, 我用了 open watcom 來測試 pascal call convention。


a.c
 1 #include <stdio.h>
 2 
 3 __declspec(__cdecl) int foo1(int a, int b)
 4 {
 5   a+=1;
 6   return a;
 7 }
 8 
 9 __declspec(__pascal) int foo2(int a, int b)
10 {
11   a+=1;
12   return a;
13 }
14 
15 __declspec(__cdecl) int foo3(int a, ...)
16 {
17   a+=1;
18   return a;
19 }
20 
21 __declspec(__pascal) int foo4(int a, ...)
22 {
23   a+=1;
24   return a;
25 }
26 
27 int main(int argc, const char *argv[])
28 {
29   int x=-1;
30 
31   x=foo3(1, 2, 3);
32   printf("x=%d\n", x);
33   x=foo4(1, 2, 3);
34   printf("x=%d\n", x);
35   x=foo1(1, 2);
36   printf("x=%d\n", x);
37   x=foo2(1, 2);
38   printf("x=%d\n", x);
39   return 0;
40 }

編譯指令:
wcl386 -c -s -d1 a.c 
wcl386 a.o

執行結果
x=2
x=4
x=2
x=2

a.dis
 1 a:     file format elf32-i386
 2 
 3 Disassembly of section .text:
 4 
 5 08048110 <_foo1>:
 6  8048110: 8b 44 24 04           mov    0x4(%esp),%eax
 7  8048114: 40                    inc    %eax
 8  8048115: c3                    ret    
 9 
10 08048116 <FOO2>:
11  8048116: 8b 44 24 08           mov    0x8(%esp),%eax
12  804811a: 40                    inc    %eax
13  804811b: c2 08 00              ret    $0x8
14 
15 0804811e <_foo3>:
16  804811e: 8b 44 24 04           mov    0x4(%esp),%eax
17  8048122: 40                    inc    %eax
18  8048123: c3                    ret    
19 
20 08048124 <FOO4>:
21  8048124: 8b 44 24 04           mov    0x4(%esp),%eax
22  8048128: 40                    inc    %eax
23  8048129: c3                    ret    
24 
25 0804812a <main_>:
26  804812a: 53                    push   %ebx
27  804812b: 51                    push   %ecx
28 
29  804812c: 6a 03                 push   $0x3
30  804812e: 6a 02                 push   $0x2
31  8048130: 6a 01                 push   $0x1
32  8048132: e8 e7 ff ff ff        call   804811e <_foo3>
33  8048137: 83 c4 0c              add    $0xc,%esp
34 
35  804813a: 50                    push   %eax
36  804813b: 68 04 b0 04 08        push   $0x804b004
37  8048140: e8 5b 00 00 00        call   80481a0 <printf_>
38  8048145: 83 c4 08              add    $0x8,%esp
39 
40  8048148: 6a 01                 push   $0x1
41  804814a: 6a 02                 push   $0x2
42  804814c: 6a 03                 push   $0x3
43  804814e: e8 d1 ff ff ff        call   8048124 <FOO4>
44  8048153: 83 c4 0c              add    $0xc,%esp
45 
46  8048156: 50                    push   %eax
47  8048157: 68 04 b0 04 08        push   $0x804b004
48  804815c: e8 3f 00 00 00        call   80481a0 <printf_>
49  8048161: 83 c4 08              add    $0x8,%esp
50 
51  8048164: 6a 02                 push   $0x2
52  8048166: 6a 01                 push   $0x1
53  8048168: e8 a3 ff ff ff        call   8048110 <_foo1>
54  804816d: 83 c4 08              add    $0x8,%esp
55 
56  8048170: 50                    push   %eax
57  8048171: 68 04 b0 04 08        push   $0x804b004
58  8048176: e8 25 00 00 00        call   80481a0 <printf_>
59  804817b: 83 c4 08              add    $0x8,%esp
60 
61  804817e: 6a 01                 push   $0x1
62  8048180: 6a 02                 push   $0x2
63  8048182: e8 8f ff ff ff        call   8048116 <FOO2>
64 
65  8048187: 50                    push   %eax
66  8048188: 68 04 b0 04 08        push   $0x804b004
67  804818d: e8 0e 00 00 00        call   80481a0 <printf_>
68  8048192: 83 c4 08              add    $0x8,%esp
69 
70  8048195: 31 c0                 xor    %eax,%eax
71  8048197: 59                    pop    %ecx
72  8048198: 5b                    pop    %ebx
73  8048199: c3                    ret    


先來看看什麼是 c call convention:
  1. 由右到左傳入 function 的參數; 所以 a.c L35 x=foo1(1, 2); 對應到 a.dis L51 ~ L54 (push $2, push $1)
  2. 由呼叫者清除堆疊; 所以 call foo1 之後, 要把 %esp 加回 8

再來看看什麼是 pascal call convention:
  1. 由左到右傳入 function 的參數; 所以 a.c L37 x=foo2(1, 2); 對應到 a.dis L61 ~ L63 (push $1, push $2)
  2. 由被呼叫者清除堆疊; 所以 a.dis L13 ret $8。

這很容易理解, 最主要我想討論三個問題:
  1. 為什麼 pascal call convention 效率好?
  2. 為什麼 pascal call convention 由被呼叫者清堆疊? c call convention 為什麼不能由被呼叫者清堆疊?
  3. 為什麼 pascal call convention 不能支援不定個數參數?
Q3:
為什麼 pascal call convention 不能支援不定個數參數?

從 a.dis L21 可得到 foo4 的參數 a 是 3, 並不是 1, 是因為編譯器產生的是 mov 0x4(%esp),%eax 所以不能得到第1個參數 a 嗎?編譯器只要產生 mov 0xc(%esp),%eax 不就可以得到 a 了, 為什麼不是這樣呢?

因為:

對於
x=foo4(1, 2, 3);
x=foo4(1, 2, 3, 4, 5);
編譯器無法同時產生
mov 0xc(%esp),%eax
mov 0x14(%esp),%eax
來得到 a, 對於一個 foo4() 只能產生同一種組合語言, 所以只好產生
mov    0x4(%esp),%eax
來得到第1個參數, 這也就會需要由右到左傳入 function 的參數。


Q2:
為什麼 pascal call convention 由被呼叫者清堆疊? c call convention 為什麼不能由被呼叫者清堆疊?

因為只能產生同一份組合語言, 這樣的 code ret $8 一定要固定。
x=foo4(1, 2, 3);
x=foo4(1, 2, 3, 4, 5);
對於這樣的程式碼來說,不能產生 return $12 又產生 return $20。
所以 pascal call convention 在不定個數參數下無法使用。
c call convention 在不使用不定個數參數下也可以由被呼叫者清除堆疊。

Q1
為什麼 pascal call convention 效率好?

ref: http://en.wikipedia.org/wiki/X86_calling_conventions#Callee_clean-up

When the callee cleans the arguments from the stack it needs to be known at
compile time how many bytes the stack needs to be adjusted. Therefore, these
calling conventions are not compatible with variable argument lists, e.g.
printf(). They may be, however, more space efficient, as the code needed to
unwind the stack does not need to be generated for each call.

因為看到 pascal call convention 效率會比較好 (不記得在那看到的), 看了 wiki, 應該是指 space efficient, 不是速度上的快。

Q1, Q2:
我看不出來 pascal call convention 效率好, 也看不出來為什麼 c call convention 不能由被呼叫者清堆疊?編譯器可以產生 push $1, push $2, 應該可以得知 ret $8, 和由右到左傳入或是由左到右傳入有什麼差異?

感謝 ptt LPH66 的回應:
http://www.ptt.cc/bbs/C_and_CPP/M.1353294706.A.6B4.html

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。