return X++的实现

发表于:2007-05-25来源:作者:点击数: 标签:X++Return相信很多C++
相信很多学C/C++语言的兄弟并没有搞清象return X++这样的语句是怎么实现的。 如果你也像我一样,眼睛里容不得半点沙,那么,这篇文章就是为你所写的。 相信很多学C/C++语言的兄弟并没有搞清象return X++这样的语句是怎么实现的。 如果你也像我一样,眼睛里容
相信很多学C/C++语言的兄弟并没有搞清象return X++这样的语句是怎么实现的。
如果你也像我一样,眼睛里容不得半点沙,那么,这篇文章就是为你所写的。

相信很多学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

原文转自:http://www.ltesting.net

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)