zoukankan      html  css  js  c++  java
  • 第17课

    第17课 - ++和--操作符分析

    1. ++和--操作符对应的两条汇编指令

    (1)前置++/--对应的两条汇编指令:变量自增(减)1;然后取变量值

    (2)后置++/--对应的两条汇编指令:先取变量值;然后变量自增(减)1

    上面两条规则很简单,那 ++-- 操作符是不是就不需要研究了呢?我们使用VS2010和gcc编译执行下面的代码,观察输出结果是否与我们预期的一致。

     1 #include <stdio.h>
     2 
     3 int main()
     4 {
     5     int i = 0;
     6     int r = 0;
     7 
     8     r = (i++) + (i++) + (i++);
     9 
    10     printf("i = %d
    ", i);
    11     printf("r = %d
    ", r);
    12 
    13     r = (++i) + (++i) + (++i);
    14 
    15     printf("i = %d
    ", i);
    16     printf("r = %d
    ", r);
    17     return 0;
    18 }

    我们的预期是:

      r = (i++) + (i++) + (i++);    // r = 0 + 1 + 2 = 3    i自增三次后 i = 3

      r = (++i) + (++i) + (++i);    // r = 4 + 5 + 6 = 15  i又自增三次后 i = 6

    使用gcc编译器编译,程序执行结果如下:

      

    使用VS2010编译器编译,程序执行结果如下:

      

    可见两款编译器对于 的输出结果与我们的预期都不同,那为何会这样呢?我们从汇编代码中寻找原因。

    VS2010汇编代码

    分析 r = (i++) + (i++) + (i++);VS2010中对应的汇编代码

      

    再分析接下来的 r = (++1) + (++i) + (++i); VS2010中对应的汇编代码如下

     

     gcc对应的汇编代码

    使用 objdump -d -S 对代码进行反汇编     暂时分析不好下面的汇编指令。。。。。等掌握了再把结果补上。。。。。。

     1     r = (i++) + (i++) + (i++);
     2   400543:    8b 55 f8                 mov    -0x8(%rbp),%edx
     3   400546:    8d 42 01                 lea    0x1(%rdx),%eax
     4   400549:    89 45 f8                 mov    %eax,-0x8(%rbp)
     5   40054c:    8b 45 f8                 mov    -0x8(%rbp),%eax
     6   40054f:    8d 48 01                 lea    0x1(%rax),%ecx
     7   400552:    89 4d f8                 mov    %ecx,-0x8(%rbp)
     8   400555:    8d 0c 02                 lea    (%rdx,%rax,1),%ecx
     9   400558:    8b 45 f8                 mov    -0x8(%rbp),%eax
    10   40055b:    8d 50 01                 lea    0x1(%rax),%edx
    11   40055e:    89 55 f8                 mov    %edx,-0x8(%rbp)
    12   400561:    01 c8                    add    %ecx,%eax
    13   400563:    89 45 fc                 mov    %eax,-0x4(%rbp)
    14 
    15 
    16     r = (++i) + (++i) + (++i);
    17   40058e:    83 45 f8 01              addl   $0x1,-0x8(%rbp)
    18   400592:    83 45 f8 01              addl   $0x1,-0x8(%rbp)
    19   400596:    8b 45 f8                 mov    -0x8(%rbp),%eax
    20   400599:    8d 14 00                 lea    (%rax,%rax,1),%edx
    21   40059c:    83 45 f8 01              addl   $0x1,-0x8(%rbp)
    22   4005a0:    8b 45 f8                 mov    -0x8(%rbp),%eax
    23   4005a3:    01 d0                    add    %edx,%eax
    24   4005a5:    89 45 fc                 mov    %eax,-0x4(%rbp)

    从上面的分析结果来看,两款编译器对于 ++ 和 -- 操作符的混合运算的处理并不相同,

    2. C标准对++和--运算符的规定

    (1)C语言只规定了 ++ -- 对应指令的相对执行次序,并没有要求两条汇编指令的执行是连续的,所以两条汇编指令中间可能会被其它指令打断!

    (2)在混合运算中,++ 和 -- 的汇编指令可能被打断执行,因此 ++ 和 -- 参与混合运算结果是不确定的,就出现了前面两款编译器的差别

    ※ 这也告诉我们,在实际工程开发中不要将 ++ 和 -- 参与到混合运算中!!!

    这里额外看下Java中对++ 和 -- 混合运算的操作

     1 class Test {
     2     public static void main(String[] args) {
     3         int i = 0;
     4         int r = 0;
     5         
     6         r = (i++) + (i++) + (i++);
     7         
     8         System.out.println("i = " + i);
     9         System.out.println("i = " + i);
    10         
    11         r = (++i) + (++i) + (++i);
    12         
    13         System.out.println("i = " + i);
    14         System.out.println("i = " + i);
    15     }
    16 }

    编译执行,可见在Java中的结果和我们之前预期的是相同的。

      

    3. 笔试面试中的奇葩题 

        

     要想知道编译器如何解释上面表达式中的++/--,首先要了解一下C编译器的贪心法

     贪心法:++/-- 表达式的阅读技巧

    (1)编译器处理的每个符号应该尽可能多的包含字符

    (2)编译器以从左向右的顺序一个一个尽可能多的读入字符

    (3)当读入的字符不可能和已读入的字符组成合法符号为止

     根据贪心法分析前面的问题:

    1 // 读入第1个字符+,因为它可以与其它符号组合,于是继续读入第2个字符+,
    2 // 编译器发现两个+形成一个自增运算符,会继续读入后面的变量i,得到++i。
    3 // 但根据贪心法,还会再读入一个字符,即++i+,表示++i后面会加上一个数,这也是有意义的。
    4 // 所以会再读入第5个字符,发现读入的字符不能变成一个有意义符号,所以停止读入字符
    5 // 然后去计算 ++i++ 这个表达式,得到1++,显然语法上是错误的
    6 ++i+++i+++i;
    7 
    8 a+++b; // 读到a++是有意义的,会再读入一个字符+,仍有意义,会再次读入b,即(a++) + b;

    再想到之前我们讲解C注释时的一个例子:

      y= x/*p;

    根据贪心算法,编译器读取到 / 之后还会再读取 *,然后就把 /* 组合在一起,就是多行注释了,当时的解决办法是再 / 前后加上空格,即 y = x / *p; 为什么这样就可以了呢?

    空格可以作为C语言中一个完整符号的休止符,编译器读入空格后立即对之前读入的符号进行处理。

  • 相关阅读:
    Collection接口
    10linux基础-Centos7系统进程管理
    09linux基础-文档归档和压缩
    05Linux基础-vim编辑器和恢复ext4下误删除的文件
    04linux系统基础-文件的基本管理和XFS文件系统备份恢复
    03Linux基础-linux基本命令操作
    02Linux基础-linux的基础操作
    01Linux基础-环境搭建
    3、函数
    1、Python基础二
  • 原文地址:https://www.cnblogs.com/shiwenjie/p/11853923.html
Copyright © 2011-2022 走看看