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:
由右到左傳入 function 的參數; 所以 a.c L35 x=foo1(1, 2); 對應到 a.dis L51 ~ L54 (push $2, push $1)
由呼叫者清除堆疊; 所以 call foo1 之後, 要把 %esp 加回 8
再來看看什麼是 pascal call convention:
由左到右傳入 function 的參數; 所以 a.c L37 x=foo2(1, 2); 對應到 a.dis L61 ~ L63 (push $1, push $2)
由被呼叫者清除堆疊; 所以 a.dis L13 ret $8。
這很容易理解, 最主要我想討論三個問題:
為什麼 pascal call convention 效率好?
為什麼 pascal call convention 由被呼叫者清堆疊? c call convention 為什麼不能由被呼叫者清堆疊?
為什麼 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 帳號。