1 ++ 和 – 操作符的本质
++ 和 – 操作符对应两条汇编指令如下:
- 前置:先对变量自增(减)1,再取变量值
- 后置:先取变量值,再对变量值自增(减)1,取的是临时变量的值
下面看一个令人头疼的问题:
上面的运行结果为多少呢,我们用编译器编译一下
1、先用 vs 编译运行,代码如下:
#include<stdio.h>
int main() {
int i = 0;
int r = 0;
r = (i++) + (i++) + (i++);
printf("i = %d, r = %d\n", i, r);
r = (++i) + (++i) + (++i);
printf("i = %d, r = %d\n", i, r);
return 0;
}
结果如下:
为什么会出现这个结果呢,右键,转入反汇编
从上面的运算可以看出:
- 对于表达式 r = (i++) + (i++) + (i++); vs 编译器的处理方法是首先将三个 i 相加赋值给 r,再将 i 自增 1 三次。
- 对于表达式 r = (++i) + (++i) + (++i); 首先将 i 自增 1 三次,再将三个 i 相加赋值给 r。
所以自然得出上面的结果。
2、再看gcc编译器的结果:
结果和 vs 编译器不同,什么原因呢,我们反汇编来看一下:
从上面的运算可以看出:
- 对于表达式 r = (i++) + (i++) + (i++); gcc 编译器和 vs 编译器的处理方法相同,首先将三个 i 相加赋值给 r,再将 i 自增 1 三次。
- 对于表达式 r = (++i) + (++i) + (++i); gcc编译器首先将 i 自增两次,两个 i 相加结果放到 eax 寄存器中,再将 i 自增 1,最后将 i 和 eax 寄存器值相加,也就是 5+5+6 = 16,最后结果赋值给 r。
3、我们再用 Java 编译器尝试一下,代码如下:
// 17-1.java
class Test {
public static void main(String[] args) {
int i = 0;
int r = 0;
r = (i++) + (i++) + (i++);
System.out.println("i = " + i);
System.out.println("r = " + r);
r = (++i) + (++i) + (++i);
System.out.println("i = " + i);
System.out.println("r = " + r);
}
}
java 编译器的处理规则如下:
(i++) + (i++) + (i++) ==> 0 + 1 + 2,i 为 3,r 为 3
(++i) + (++i) + (++i) ==> 4 + 5 + 6,i 为 6,r 为 15
为什么同样的表达式结果不同呢,其实这是 C 语言的灰色地带,标准 C 语言的规则如下:
++ 和 – 操作符使用分析:
- C 语言只规定了 ++ 和 – 对应指令的相对执行次序
- ++ 和 – 对应的汇编指令不一定连续运行
- 在混合运算中,++ 和 – 的汇编指令可能被打断
由于++ 和 – 的汇编指令可能被打断,所以,++ 和 – 参加混合运算结果是不确定的,看编译器如何实现。
每个编译器的规则不同,为什么还要去学呢,我们写代码的质量和了解编译器的熟悉程度有关,更多的了解编译器的规则,有助于避免 bug。
2 ++ 和 – 操作符使用分析
下面看一个面试中的奇葩问题。
面试官为什么会出这样的题呢,其实是想考我们对 C 语言编译器的掌握程度,下面就来说一下编译器如何处理表达式。
贪心法:++ 和 – 表达式的阅读技巧
- 编译器处理的每个符号应该进可能多的包含字符
- 编译器以从左向右的顺序一个一个尽可能多的读入字符
- 当读入的字符不可能和已读入的字符组成合法符号为止
// 17-2.c
#include<stdio.h>
int main()
{
int i = 0;
int j = ++i+++i+++i;
int a = 1;
int b = 4;
int c = a+++b;
int* p = &a;
// b = b/*p;
printf("i = %d\n", i);
printf("j = %d\n", j);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
return 0;
}
第 6 行报错了,下面通过贪心法分析一下错误:
表达式读到 ++i+++i+++i 时,尽可能多的读入字符,先处理++i++,再读就不合法了,++i++ 先计算前置++,变成 1++,这是不合法的。
- int j = ++i+++i+++i; ==> ++i++ ==> 1++
再来看看第 9 行,int c = a+++b; 编译器读入a++ 后还能在读,再读一个 +,也可能合法,再读入 b,编译器先算 a++,再算 +b
- a+++b ==> 1 + 4 ==> 5, a 为 2,c 为 5
同样,表达式 b = b/*p; 中 /* 会被当作注释
对于上面的代码如果我们加上空格,再来编译
// 17-2.c
#include<stdio.h>
int main()
{
int i = 0;
int j = ++i + ++i + ++i;
int a = 1;
int b = 4;
int c = a++ + b;
int* p = &a;
b = b/ *p;
printf("i = %d\n", i);
printf("j = %d\n", j);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
return 0;
}
为什么加上空格,编译器就可以正常编译运行了呢
空格的作用:
- 空格可以作为 C 语言中一个完整符号的休止符
- 编译器读入空格后立即对之前读入的符号进行处理
编译器读入 ++i + ++i + ++i; 后读入 ++i 后马上处理,相同的,读入 b/ *p 时,读入空格直接将 / 作为除法操作处理。
3 小结
1、++ 和 – 操作符在混合运算中的行为可能不同
2、编译器通过贪心法处理表达式中的子表达式
3、空格可以作为 C 语言中的一个完整符号的休止符,编译器读入空格后立即对之前读入的符号进行处理