i++、++i、i=i+1、效率怎么样?看过一本书上说,i++比i=i+1好
的地方是因为i=i+1中的那个1要占用一个寄存器,所以速度没有i++快,于是我想验证一下。另外,以前听说过Java中的“i=i++”得不到正确结论,也就是应该是“先累加再赋值”,但Java经过这种运算后,i值居然没有变化。所以在这里,想一并把这几个问题在C中验证一下。 =====================测试的C源程序====================#01: #include <stdio.h> #02: #03: main() #04: { #05: int i=0, j=0; #06: i=i++; #07: #08: i=i+1; #09: i++; #10: ++i; #11: #12: j=i+1; #13: j=i++; #14: j=++i; #15: #16: printf("i=%d", i); #17: }
======================================================
下面是我在 VC++ 6.0 + SP5 / Window 2000环境下对上述源程序的反汇编:
5: int i=0, j=0;0040D698 mov dword ptr [ebp-4],00040D69F mov dword ptr [ebp-8],0
6: i=i++;0040D6A6 mov eax,dword ptr [ebp-4]0040D6A9 mov dword ptr [ebp-4],eax0040D6AC mov ecx,dword ptr [ebp-4]0040D6AF add ecx,10040D6B2 mov dword ptr [ebp-4],ecx
8: i=i+1;0040D6B5 mov edx,dword ptr [ebp-4]0040D6B8 add edx,10040D6BB mov dword ptr [ebp-4],edx
9: i++;0040D6BE mov eax,dword ptr [ebp-4]0040D6C1 add eax,10040D6C4 mov dword ptr [ebp-4],eax
10: ++i;0040D6C7 mov ecx,dword ptr [ebp-4]0040D6CA add ecx,10040D6CD mov dword ptr [ebp-4],ecx
12: j=i+1;0040D6D0 mov edx,dword ptr [ebp-4]0040D6D3 add edx,10040D6D6 mov dword ptr [ebp-8],edx
13: j=i++;0040D6D9 mov eax,dword ptr [ebp-4]0040D6DC mov dword ptr [ebp-8],eax0040D6DF mov ecx,dword ptr [ebp-4]0040D6E2 add ecx,10040D6E5 mov dword ptr [ebp-4],ecx
14: j=++i;0040D6E8 mov edx,dword ptr [ebp-4]0040D6EB add edx,10040D6EE mov dword ptr [ebp-4],edx0040D6F1 mov eax,dword ptr [ebp-4]0040D6F4 mov dword ptr [ebp-8],eax
=================================================================下面是我在 SCO UNIX下用cc -g 对上述源程序编译后,用dbx打出的内存汇编代码:
( int i=0, j=0; )0x0000015e (main:5) mov DWord Ptr [ebp-0x04],$00x00000165 (main:5) mov DWord Ptr [ebp-0x08],$0
( i=i++; )0x0000016c (main:6) mov eax,DWord Ptr [ebp-0x04]0x0000016f (main:6) inc DWord Ptr [ebp-0x04]0x00000172 (main:6) mov DWord Ptr [ebp-0x04],eax
( i=i+1; )0x00000175 (main:8) inc DWord Ptr [ebp-0x04]
( i++; )0x00000178 (main:9) inc DWord Ptr [ebp-0x04]
( ++i; )0x0000017b (main:10) inc DWord Ptr [ebp-0x04]
( j=i+1; )0x0000017e (main:12) mov eax,DWord Ptr [ebp-0x04]0x00000181 (main:12) inc eax0x00000182 (main:12) mov DWord Ptr [ebp-0x08],eax
( j=i++; )0x00000185 (main:13) mov eax,DWord Ptr [ebp-0x04]0x00000188 (main:13) inc DWord Ptr [ebp-0x04]0x0000018b (main:13) mov DWord Ptr [ebp-0x08],eax
( j=++i; )0x0000018e (main:14) inc DWord Ptr [ebp-0x04]0x00000191 (main:14) mov eax,DWord Ptr [ebp-0x04]0x00000194 (main:14) mov DWord Ptr [ebp-0x08],eax
======================================================================
1、从上述的汇编代码我们不难看出对于i=i+1; i++; ++i这三个指令的汇编
指令无论是在VC下还是在SCO UNIX的cc下都是一样的(虽然这两个编译器 对其汇编得到的指令不太一样,但是它们对这三条指令的汇编都是一样的, 这里,我是关闭编译器优化的选项,也许现在的编译器自动对之优化了)在VC下都是:
0040D6B5 mov edx,dword ptr [ebp-4] 0040D6B8 add edx,1 0040D6BB mov dword ptr [ebp-4],edx在cc下都是:
0x0000017b (main:10) inc DWord Ptr [ebp-0x04]2、对于复合指令 j=i+1; j=i++; 却有所不同,
在VC下:j=i+1 是三条指令,而 j=i++ 却有五条指令,这也很合理。 在SCO下: j=i+1 和 j=i++ 都是三条指令。(j=i++指令数比VC少)3、对于i=i++,在VC下可以得到正确的结果 i=1;而在SCO下却是i=0; 这可以
从其汇编看到。VC对i=i++的汇编是:
0040D6A6 mov eax,dword ptr [ebp-4] //取内存值i到eax 0040D6A9 mov dword ptr [ebp-4],eax //把eax值放到内存i中 0040D6AC mov ecx,dword ptr [ebp-4] //取内存值i到ecx 0040D6AF add ecx,1 //寄存器ecx加1 0040D6B2 mov dword ptr [ebp-4],ecx //把ecx值放到内存i中
SCO对i=i++的汇编是:
//取内存i到寄存器eax中 0x0000016c (main:6) mov eax,DWord Ptr [ebp-0x04]//对内存i进行累加 0x0000016f (main:6) inc DWord Ptr [ebp-0x04] //把寄存器eax的值放到内存i中 0x00000172 (main:6) mov DWord Ptr [ebp-0x04],eax
可见,之所以SCO得不到正确的结果的原因了。我已为其加上了注释,相信各位是看得懂的。
【结论】:
1、如果是单语句,无论是i=i+1; i++; ++i;其效率是完全一样的。 2、之所以SCO下的i=i++得不到正确结果,是因为其编译器追求效率的结果。(Java亦如此) 3、观察SCO下的汇编指令“inc DWord Ptr [ebp-0x04]”,难道可以直接操作内存吗?(拿不准) 4、各个产商的编译器各有不同,所产生的语句的指令代码也有所不同,C++就更尤其如此啦。======================================================================
最后附上Java对i=i++的测试
源程序:
#1: class Test #2: { #3: public static void main(String[] argv) #4: { #5: int i=0; #6: i=i++; #7: System.out.println("i="+i); #8: } #9: }
用javac -g Test.java 把其编译成 Test.class。再用javap -c Test打出其虚拟机指令代码如下:D:/>javap -c Test
Compiled from Test.javaclass Test extends java.lang.Object { Test(); public static void main(java.lang.String[]);}
Method Test() 0 aload_0 1 invokespecial #1 <Method java.lang.Object()> 4 return
Method void main(java.lang.String[]) 0 iconst_0 1 istore_1 2 iload_1 3 iinc 1 1 6 istore_1 7 getstatic #2 <Field java.io.PrintStream out> 10 new #3 <Class java.lang.StringBuffer> 13 dup 14 invokespecial #4 <Method java.lang.StringBuffer()> 17 ldc #5 <String "i="> 19 invokevirtual #6 <Method java.lang.StringBuffer append(java.lang.String)> 22 iload_1 23 invokevirtual #7 <Method java.lang.StringBuffer append(int)> 26 invokevirtual #8 <Method java.lang.String toString()> 29 invokevirtual #9 <Method void println(java.lang.String)> 32 return
可见其中的:
1 istore_1 2 iload_1 3 iinc 1 1 6 istore_1就是i=i++的虚拟机指令,想来一定和SCO的编译器有异曲同工之处。【备注】
这段程序的结果是i=0,我是在 j2se 1.4.0 下进行的测试。 虽然说,这是一个BUG,但是有多少人又会写i=i++这种无聊的语句呢? 不过却可以了解一下编译器(解释器)的工作方式。