第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\n", i);
11     printf("r = %d\n", r);
12 
13     r = (++i) + (++i) + (++i);
14 
15     printf("i = %d\n", i);
16     printf("r = %d\n", 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语言中一个完整符号的休止符,编译器读入空格后立即对之前读入的符号进行处理。

 

posted @ 2019-11-13 22:52  Hengs  阅读(477)  评论(0编辑  收藏  举报