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],0
0040D69F   mov         dword ptr [ebp-8],0

6:        i=i++;
0040D6A6   mov         eax,dword ptr [ebp-4]
0040D6A9   mov         dword ptr [ebp-4],eax
0040D6AC   mov         ecx,dword ptr [ebp-4]
0040D6AF   add         ecx,1
0040D6B2   mov         dword ptr [ebp-4],ecx

8:        i=i+1;
0040D6B5   mov         edx,dword ptr [ebp-4]
0040D6B8   add         edx,1
0040D6BB   mov         dword ptr [ebp-4],edx

9:        i++;
0040D6BE   mov         eax,dword ptr [ebp-4]
0040D6C1   add         eax,1
0040D6C4   mov         dword ptr [ebp-4],eax

10:       ++i;
0040D6C7   mov         ecx,dword ptr [ebp-4]
0040D6CA   add         ecx,1
0040D6CD   mov         dword ptr [ebp-4],ecx

12:       j=i+1;
0040D6D0   mov         edx,dword ptr [ebp-4]
0040D6D3   add         edx,1
0040D6D6   mov         dword ptr [ebp-8],edx

13:       j=i++;
0040D6D9   mov         eax,dword ptr [ebp-4]
0040D6DC   mov         dword ptr [ebp-8],eax
0040D6DF   mov         ecx,dword ptr [ebp-4]
0040D6E2   add         ecx,1
0040D6E5   mov         dword ptr [ebp-4],ecx

14:       j=++i;
0040D6E8   mov         edx,dword ptr [ebp-4]
0040D6EB   add         edx,1
0040D6EE   mov         dword ptr [ebp-4],edx
0040D6F1   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],$0
0x00000165 (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       eax
0x00000182 (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.java
class 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++这种无聊的语句呢?
    不过却可以了解一下编译器(解释器)的工作方式。

Inside i++的更多相关文章

  1. vsftpd:500 OOPS: vsftpd: refusing to run with writable root inside chroot ()错误的解决方法

    ---恢复内容开始--- 最近在安装了vsftpd后 添加了虚拟账户后 新建用户 为新用户创立独立的工作目录 因为虚拟用户在工作目录需要上传文件 所以必须拥有此目录的W权限,但每当给此目录加上W权限后 ...

  2. 解决vsftpd的refusing to run with writable root inside chroot错误

    参考 http://www.cnblogs.com/CSGrandeur/p/3754126.html 在Ubuntu下用 vsftpd 配置FTP服务器,配置 “ sudo chmod a-w /h ...

  3. 《Inside UE4》目录

    <Inside UE4>目录 InsideUE4 UE4无疑是非常优秀的世界上最顶尖的引擎之一,性能和效果都非常出众,编辑器工作流也非常的出色,更难得宝贵的是完全的开源让我们有机会去从中吸 ...

  4. 在子线程中new Handler报错--Can't create handler inside thread that has not called Looper.prepare()

    在子线程中new一个Handler为什么会报以下错误? java.lang.RuntimeException:  Can't create handler inside thread that has ...

  5. 《Inside UE4》-0-开篇

    <Inside UE4>-0-开篇 InsideUE4   前言 VR行业发展是越来越火热了,硬件设备上有HTC VIVE,Oculus rift,PS VR,各种魔镜:平台上有Steam ...

  6. 500 OOPS: vsftpd: refusing to run with writable root inside chroot()

    Ubuntu 12.04 64bit系统下安装的vsftpd,在登陆时提示500 OOPS: vsftpd: refusing to run with writable root inside chr ...

  7. 《Inside UE4》-1-基础概念

    <Inside UE4>-1-基础概念   InsideUE4   创建测试项目 接上文的准备工作,双击生成的UE4Editor.exe,选择创建测试C++空项目Hello(以后的源码分析 ...

  8. 《Inside UE4》-2-GamePlay架构(一)Actor和Component

    <Inside UE4>-2-GamePlay架构(一)Actor和Component     <Inside UE4>-2-GamePlay架构(一)Actor和Compon ...

  9. VS代码段扩展Snippet Designer is a Visual Studio plug in which allows you to create and search for snippets inside the IDE

    Snippet Designer is a Visual Studio plug in which allows you to create and search for snippets insid ...

  10. Android 线程更新UI报错 : Can't create handler inside thread that has not called Looper.prepare()

    MainActivity中有一个按钮,绑定了save方法 public void save(View view) { String title = titleText.getText().toStri ...

随机推荐

  1. 在CentOS 7上安装Nginx服务器

    下面我就我在CentOS上安装Nginx经验做简单的记录,以备后查. 1.下载nginx-release包 以CentOS 7为例,下载nginx软件包:http://nginx.org/packag ...

  2. C#.NET常见问题(FAQ)-如何修改Form不能修改窗体大小

    把FormBorderSytle改一下就可以了,改成FixedSingle或者Fixed3D都可以   更多教学视频和资料下载,欢迎关注以下信息: 我的优酷空间: http://i.youku.com ...

  3. iOS xcodebuile 自动编译打包ipa

    xcodebuild -alltargets clean 首先进入到工程文件所在的目录.比如我的文件某个工程放在(.xcodeproj文件所在的目录) /Users/xxx/xxx 然后ce /Use ...

  4. Read from socket failed: Connection reset by peer.

    复制密钥另一台主机时,出现了错误: Read from socket failed: Connection reset by peer. 到被登录主机的/var/log/auth.log查看日志: M ...

  5. DOM笔记(十):JavaScript正則表達式

    一.RegExp ECMAScript通过RegExp类型类支持正則表達式,语法和Perl类似: var exp = /pattern/flags; patternb部分是不论什么简单的或复杂的正則表 ...

  6. Python调用Windows外部程序

    在Python中可以方便地使用os模块运行其他的脚本或者程序,这样就可以在脚本中直接使用其他脚本,或者程序提供的功能,而不必再次编写实现该功能的代码.为了更好地控制运行的进程,可以使用win32pro ...

  7. oracle 12c 官方文档 及软件下载

    oracle 12c 官方文档 http://www.oracle.com/pls/db121/homepage oracle 12c 软件下载 http://www.oracle.com/techn ...

  8. 亲历H5移动端游戏微信支付接入及那些坑(二)——获取Openid和授权

    第一篇中将一些坑说明,那么这篇开始正式进入接入步骤.具体的参数说明,我不会列出,毕竟微信官方文档都有,我想大家都看的懂,而且这接口也有可能微信会变动,所以不列出来,也是不想引起大家的误解,接入步骤只起 ...

  9. 改变mysql客户端输出的字符串编码

    在客户端改变中文输出的编码,通常以gbk输出,因为电脑常见的是gbk编号形式 目的:不改变编码,输出中文的时候,可能会出现乱码的情况, set names gbk 在客户端以gbk编码显示需要输出的内 ...

  10. mysql保留2位小数字段如何设置 浮点数

    保留2位小数字段如何设置 方法1: select cast(avg(75.3333333) as decimal(10,2)) as '平均分' );#format第一个参数传递浮点数