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