[IMG_202011]
fig 1. C++ 函數式編程
有一陣時間, 沒有去研究一個技術主題之後, 我又回到了 revursive 上, 這是在讀 SICP
之後才注意到的技術。
但是 revursive 實在太難, 我還沒能掌握這個思考方式。
甚至連 tail call (tail recursive) 都搞不清楚, 後來在閱讀了「C++ 函數式編程
(Functional Programming in C++: How to improve your C++ programs using
functional techniques)」(fig 1) 之後, 才知道就是字面上的意思。
而只有 tail call 才能讓編譯器有機會轉成 loop, 避免使用 stack。
一般看到的文章都說編譯器會把 tail call 轉成 loop, 而不使用 stack 空間, 避免
stack 爆掉, 這是真的嗎?
要怎麼確認呢?
就像 inline function, 我要怎麼確認真的有 inline 呢?
如果編譯器沒有給出明確訊息, 似乎也只能看編譯器輸出的組合語言了。
我用了 sum.c 來測試, 這個程式就是把 arr[0] ~ arr[4] 印出來, 只是我用了
recursive function 印出, 而不是用 for loop。
這個程式符合 tail call, call 自己的那行程式是在程式的最尾端, 所以應該可以被編
譯
器最佳化為 loop 才是。但如果沒下什麼編譯選項的話, 是看不到這樣的結果的。
list 1. sum.c
1 #include <stdio.h>
2
3 void print(int *array, int len, int n)
4 {
5 if (n >= len)
6 {
7 return;
8 }
9 else
10 {
11 printf("n: %d, %d\n", n, array[n]);
12 return print(array, len, n+1);
13 }
14 }
15
16 int main(int argc, char *argv[])
17 {
18 int arr[] = {1,2,3,4,5,};
19 print(arr, 5, 0);
20 return 0;
21 }
而和 tail recursive calls 有關的編譯選項是 -foptimize-sibling-calls。
-foptimize-sibling-calls
Optimize sibling and tail recursive calls.
Enabled at levels -O2, -O3, -Os.
但是很奇怪, 如果使用 -foptimize-sibling-calls 反而不會輸出 Optimize tail
recursive calls 的組合語言, 需要用 -Os 才會將 tail recursive calls 轉成 loop。
gcc -no-pie -m32 sum.c -g -Os -o sum.tc.opt
gcc -no-pie -m32 sum.c -g -foptimize-sibling-calls -o sum.no.tc.opt
list 2 L37, 在 print() 裡頭還是會 call print()
list 2. sum.no.tc.opt.txt
3
4 sum.no.tc.opt: file format elf32-i386
5
6
7
8 08049162 <print>:
9 8049162: 55 push %ebp
10 8049163: 89 e5 mov %esp,%ebp
11 8049165: 53 push %ebx
12 8049166: 83 ec 04 sub $0x4,%esp
13 8049169: e8 b4 00 00 00 call 8049222 <__x86.get_pc_thunk.ax>
14 804916e: 05 92 2e 00 00 add $0x2e92,%eax
15 8049173: 8b 55 10 mov 0x10(%ebp),%edx
16 8049176: 3b 55 0c cmp 0xc(%ebp),%edx
17 8049179: 7d 43 jge 80491be <print+0x5c>
18 804917b: 8b 55 10 mov 0x10(%ebp),%edx
19 804917e: 8d 0c 95 00 00 00 00 lea 0x0(,%edx,4),%ecx
20 8049185: 8b 55 08 mov 0x8(%ebp),%edx
21 8049188: 01 ca add %ecx,%edx
22 804918a: 8b 12 mov (%edx),%edx
23 804918c: 83 ec 04 sub $0x4,%esp
24 804918f: 52 push %edx
25 8049190: ff 75 10 pushl 0x10(%ebp)
26 8049193: 8d 90 08 e0 ff ff lea -0x1ff8(%eax),%edx
27 8049199: 52 push %edx
28 804919a: 89 c3 mov %eax,%ebx
29 804919c: e8 8f fe ff ff call 8049030 <[email protected]>
30 80491a1: 83 c4 10 add $0x10,%esp
31 80491a4: 8b 45 10 mov 0x10(%ebp),%eax
32 80491a7: 83 c0 01 add $0x1,%eax
33 80491aa: 83 ec 04 sub $0x4,%esp
34 80491ad: 50 push %eax
35 80491ae: ff 75 0c pushl 0xc(%ebp)
36 80491b1: ff 75 08 pushl 0x8(%ebp)
37 80491b4: e8 a9 ff ff ff call 8049162 <print>
38 80491b9: 83 c4 10 add $0x10,%esp
39 80491bc: eb 01 jmp 80491bf <print+0x5d>
40 80491be: 90 nop
41 80491bf: 8b 5d fc mov -0x4(%ebp),%ebx
42 80491c2: c9 leave
43 80491c3: c3 ret
44
45 080491c4 <main>:
46 80491c4: 8d 4c 24 04 lea 0x4(%esp),%ecx
47 80491c8: 83 e4 f0 and $0xfffffff0,%esp
48 80491cb: ff 71 fc pushl -0x4(%ecx)
49 80491ce: 55 push %ebp
50 80491cf: 89 e5 mov %esp,%ebp
51 80491d1: 51 push %ecx
52 80491d2: 83 ec 24 sub $0x24,%esp
53 80491d5: e8 48 00 00 00 call 8049222 <__x86.get_pc_thunk.ax>
54 80491da: 05 26 2e 00 00 add $0x2e26,%eax
55 80491df: c7 45 e4 01 00 00 00 movl $0x1,-0x1c(%ebp)
56 80491e6: c7 45 e8 02 00 00 00 movl $0x2,-0x18(%ebp)
57 80491ed: c7 45 ec 03 00 00 00 movl $0x3,-0x14(%ebp)
58 80491f4: c7 45 f0 04 00 00 00 movl $0x4,-0x10(%ebp)
59 80491fb: c7 45 f4 05 00 00 00 movl $0x5,-0xc(%ebp)
60 8049202: 83 ec 04 sub $0x4,%esp
61 8049205: 6a 00 push $0x0
62 8049207: 6a 05 push $0x5
63 8049209: 8d 45 e4 lea -0x1c(%ebp),%eax
64 804920c: 50 push %eax
65 804920d: e8 50 ff ff ff call 8049162 <print>
66 8049212: 83 c4 10 add $0x10,%esp
67 8049215: b8 00 00 00 00 mov $0x0,%eax
68 804921a: 8b 4d fc mov -0x4(%ebp),%ecx
69 804921d: c9 leave
70 804921e: 8d 61 fc lea -0x4(%ecx),%esp
71 8049221: c3 ret
list 3 L65, 在 print() 是用 jmp 回到前面一個點 (L55), 就是 loop 的動作。
list 3. sum.tc.opt.txt
1
2 sum.tc.opt: file format elf32-i386
3
4
5
6 Disassembly of section .text:
7
8 08049050 <main>:
9 8049050: e8 9b 01 00 00 call 80491f0 <__x86.get_pc_thunk.ax>
10 8049055: 05 ab 2f 00 00 add $0x2fab,%eax
11 804905a: 8d 4c 24 04 lea 0x4(%esp),%ecx
12 804905e: 83 e4 f0 and $0xfffffff0,%esp
13 8049061: ff 71 fc pushl -0x4(%ecx)
14 8049064: 55 push %ebp
15 8049065: 89 e5 mov %esp,%ebp
16 8049067: 57 push %edi
17 8049068: 56 push %esi
18 8049069: 8d b0 14 e0 ff ff lea -0x1fec(%eax),%esi
19 804906f: 8d 45 d4 lea -0x2c(%ebp),%eax
20 8049072: 51 push %ecx
21 8049073: 8d 7d d4 lea -0x2c(%ebp),%edi
22 8049076: b9 05 00 00 00 mov $0x5,%ecx
23 804907b: 83 ec 30 sub $0x30,%esp
24 804907e: f3 a5 rep movsl %ds:(%esi),%es:(%edi)
25 8049080: 6a 00 push $0x0
26 8049082: 6a 05 push $0x5
27 8049084: 50 push %eax
28 8049085: e8 28 01 00 00 call 80491b2 <print>
29 804908a: 8d 65 f4 lea -0xc(%ebp),%esp
30 804908d: 31 c0 xor %eax,%eax
31 804908f: 59 pop %ecx
32 8049090: 5e pop %esi
33 8049091: 5f pop %edi
34 8049092: 5d pop %ebp
35 8049093: 8d 61 fc lea -0x4(%ecx),%esp
36 8049096: c3 ret
37 8049097: 66 90 xchg %ax,%ax
38 8049099: 66 90 xchg %ax,%ax
39 804909b: 66 90 xchg %ax,%ax
40 804909d: 66 90 xchg %ax,%ax
41 804909f: 90 nop
42
43
44 080491b2 <print>:
45 80491b2: 55 push %ebp
46 80491b3: 89 e5 mov %esp,%ebp
47 80491b5: 57 push %edi
48 80491b6: 56 push %esi
49 80491b7: 53 push %ebx
50 80491b8: e8 33 ff ff ff call 80490f0 <__x86.get_pc_thunk.bx>
51 80491bd: 81 c3 43 2e 00 00 add $0x2e43,%ebx
52 80491c3: 83 ec 0c sub $0xc,%esp
53 80491c6: 8b 75 10 mov 0x10(%ebp),%esi
54 80491c9: 8d bb 08 e0 ff ff lea -0x1ff8(%ebx),%edi
55 80491cf: 3b 75 0c cmp 0xc(%ebp),%esi
56 80491d2: 7d 14 jge 80491e8 <print+0x36>
57 80491d4: 50 push %eax
58 80491d5: 8b 45 08 mov 0x8(%ebp),%eax
59 80491d8: ff 34 b0 pushl (%eax,%esi,4)
60 80491db: 56 push %esi
61 80491dc: 46 inc %esi
62 80491dd: 57 push %edi
63 80491de: e8 4d fe ff ff call 8049030 <[email protected]>
64 80491e3: 83 c4 10 add $0x10,%esp
65 80491e6: eb e7 jmp 80491cf <print+0x1d>
66 80491e8: 8d 65 f4 lea -0xc(%ebp),%esp
67 80491eb: 5b pop %ebx
68 80491ec: 5e pop %esi
69 80491ed: 5f pop %edi
70 80491ee: 5d pop %ebp
71 80491ef: c3 ret
看到這樣的結果就安心了, 真的是像文章上說的一樣, 有做 tail recursive calls
Optimization。