相信很多學C/C++語言的兄弟并沒有搞清象return X++這樣的語句是怎么實現的。
如果你也像我一樣,眼睛里容不得半點沙,那么,這篇文章就是為你所寫的。
情況一:X是一簡單類型
文件t.c內容如下:
int f(int a)
{
return a++;
}
int main()
{
int v = 1;
f(v);
return 0;
}
一般情況這種寫法在c語言中其實沒有任何意義,看一下轉化成匯編的結果(用命令gcc -S t.c)
1 .file "t.c"
2 .text
3 .globl f
4 .type f, @function
5 f:
6 pushl %ebp
7 movl %esp, %ebp
8 movl 8(%ebp), %eax
9 incl 8(%ebp)
10 popl %ebp
11 ret
12 .size f, .-f
13 .globl main
14 .type main, @function
15 main:
16 pushl %ebp
17 movl %esp, %ebp
18 subl , %esp
19 andl $-16, %esp
20 movl , %eax
21 addl , %eax
22 addl , %eax
23 shrl , %eax
24 sall , %eax
25 subl %eax, %esp
26 movl , -4(%ebp)
27 movl -4(%ebp), %eax
28 movl %eax, (%esp)
29 call f
30 movl , %eax
31 leave
32 ret
33 .size main, .-main
34 .ident "GCC: (GNU) 4.0.0 (Gentoo Linux 4.0.0)"
35 .section .note.GNU-stack,"",@progbits
我們看函數f的實現。第八行把形參的值賦給寄存器%eax,linux下的AT&T匯編規定%eax中的內容是函數的返回值,這樣就相當于直接把形參的返回了。而第九行的指令根本就沒有作用,因為函數的形參是局部的auto變量,在函數的運行棧上分配空間。這樣,即使在函數f中改變了形參的值,當函數返回后,這次改變就沒有意義了。
可以看到,如果用-O優化的話,第九行的指令就被優化掉了。
命令gcc -S -O3 t.c的結果如下
1 .file "t.c"
2 .text
3 .p2align 4,,15
4 .globl f
5 .type f, @function
6 f:
7 pushl %ebp
8 movl %esp, %ebp
9 movl 8(%ebp), %eax
10 popl %ebp
11 ret
12 .size f, .-f
13 .p2align 4,,15
14 .globl main
15 .type main, @function
16 main:
17 pushl %ebp
18 xorl %eax, %eax
19 movl %esp, %ebp
20 subl , %esp
21 andl $-16, %esp
22 subl , %esp
23 leave
24 ret
25 .size main, .-main
26 .ident "GCC: (GNU) 4.0.0 (Gentoo Linux 4.0.0)"
27 .section .note.GNU-stack,"",@progbits
看到沒有,函數f的核心指令就剩下一條了,剛才的那條沒有用的指令就被優化掉了。
管中窺豹,可見一斑。我之所以把整個結果再次帖出來,就是為了讓兄弟們看一下,編譯器的優化效果有多么強!原來很煩瑣的指令,一下就變得簡單明了了,優化后程序的執行速度當然就可想而知了:)
情形二:X是指針類型
這種情況就比原來要復雜一點點了,源程序改為如下形式:
#include
int f(int *a)
{
return (*a)++;
}
int main()
{
int v = 1;
f(&v);
return 0;
}
$ gcc -S -O3 t.c后的結果為:
1 .file "t.c"
2 .text
3 .p2align 4,,15
4 .globl f
5 .type f, @function
6 f:
7 pushl %ebp
8 movl %esp, %ebp
9 movl 8(%ebp), %edx #取形參,第N個形參在棧上的位置為(N+1)*4+%ebp
10 movl (%edx), %eax #把形參指向的地址單元的值賦給%eax
11 leal 1(%eax), %ecx #把%eax中的值加1后賦給%ecx
12 movl %ecx, (%edx) #運算結果寫回形參指向的單元,即完成了(*a)++
13 popl %ebp
14 ret
15 .size f, .-f
16 .p2align 4,,15
17 .globl main
18 .type main, @function
19 main:
20 pushl %ebp
21 xorl %eax, %eax
22 movl %esp, %ebp
23 subl , %esp
24 andl $-16, %esp
25 subl , %esp
26 leave
27 ret
28 .size main, .-main
29 .ident "GCC: (GNU) 4.0.0 (Gentoo Linux 4.0.0)"
30 .section .note.GNU-stack,"",@progbits
其實也不是很麻煩。函數f的核心語句只有四條指令,看上面的注釋就行了。
如果沒有看匯編結果,有的兄弟一定會犯糊涂了:為什么函數返回了還能有效改變變量的值?
看了匯編就一清二楚了:其實變量的值仍然是在函數體中改變的,只不過返回的值和改變的值沒有什么必然的聯系而已。
如果不加優化參數,函數f轉化的匯編的結果如下,晦澀多了吧
5 f:
6 pushl %ebp
7 movl %esp, %ebp
8 movl 8(%ebp), %eax #取形參
9 movl (%eax), %eax #取形參所指向的地址單元的值
10 movl %eax, %ecx #保存%eax的副本
11 leal 1(%eax), %edx #把%eax中的值加1后放入%edx
12 movl 8(%ebp), %eax #再次取形參,下一步用
13 movl %edx, (%eax) #把%edx的值存入%eax指向的單元,即完成了(*a)++
14 movl %ecx, %eax #恢復%eax的值。再次強調,函數的返回值保存在%eax中
15 popl %ebp
16 ret