【C进阶】17、++和--操作符分析

Summary 1)++和--参与混合运算结果是不确定的,如r = (i++) + (i++); 等

  • C++只规定了++和--对应指令的相对执行次序取值和自增的相对顺序)
  • ++和--对应的汇编指令不一定连续执行
  • 在混合运算中,++和--的汇编指令可能被打断执行 (取值和自增可能被打断了,中间插入了其他代码)
2)编译器的贪心法
  • 编译器以从左向右的顺序,一个一个地尽可能多的读入字符
  • 当读入的字符不可能和已经读入的字符组成合法符号为止
3)空格可以作为C语言中一个完整符号的休止符编译器读入空格后会立即对之前读入的符号进行处理
++和--操作符剖析 1、Demo1
  • 以下代码的输出是?
    int i = 0; int r = 0; r = (i++) + (i++) + (i++); printf("i = %d\n, r = %d\n", i, r); r = (++i) + (++i) + (++i); printf("i = %d\n, r = %d\n", i, r);

  • 代码初步分析:
    第一行输出:i = 3, r = 3; // 按照从左向右的计算方法,i先取值,再自增, // 则r = 0 + 1 + 2 = 3; 第二行输出:i = 6, r = 16; // 同上,r = 4 + 5 + 6 = 15;

以上代码在VS2015编译器的实际输出结果为:
i = 3, r = 0; i = 6, r = 18;

借助Vs编译器查看反汇编结果:
【C进阶】17、++和--操作符分析
文章图片

代码分析:
对于r = (i++) + (i++) + (i++); // 先取了i的值做了相加,赋值给r;然后i自增3次 在汇编层面,做的操作依次是: 1)00C542BC mov 将i代表的这个地址中的值放到寄存器eax中,为0 2)00C542BF add eax的值和i的值累加,累加和仍然为0 3)00C542C5 mov 将eax里的值放到r变量代表的内存里, 所以r的值为0 4)后面做了3次相同的操作:将i变量内存里的值放到ecx寄存器里,然后加1, 再把ecx里的值放回i变量的内存里;重复2次 所以i的值为3对于r = (i++) + (i++) + (i++); // 先给i自增了3次;然后把i的值加3次给了r 在汇编层面,做的操作依次是: 1)009A42F8 到 009A42FE 将i代表的这个地址中的值放到寄存器eax中; 然后eax里的值自增1; 写回i的内存里,此时i的值为4 重复2次后,i的值为6 所以i的值为6 2)009A4313 mov 把i里的值移动到eax寄存器中,eax里的值为6 3)009A4316 add eax里的值加上i的值,即6+6,eax里的值为12 4)009A4319 add eax里的值加上i的值,即12+6,eax里的值为18 5)009A4319 mov eax里的值写回r代表的内存里 所以r的值为18

这一段代码,我们的分析和实际的编译器结果大不相同;那么其他编译器又如何?
bcc编译器和vc编译器的结果一致,而gcc编译器的结果则是“r = 0 和 r = 16”
以上代码在java编译器中的输出结果:
// test.java public static void main(String[] args) { System.out.println("test.java file name must be equal to the class name test"); int i = 0; int r = 0; r = (i++) + (i++) + (i++); System.out.println("i = " + i + ", r = " + r); r = (++i) + (++i) + (++i); System.out.println("i = " + i + ", r = " + r); }// 编译:javac test.java // 执行:java test // 输出:i = 3, r = 3 i = 6, r = 15和分析的一致

以上的测试说明:++和--参与混合运算结果是不确定的
  • C++只规定了++和--对应指令的相对执行次序
  • ++和--对应的汇编指令不一定连续执行
  • 在混合运算中,++和--的汇编指令可能被打断执行
2、Demo2
  • 以下代码的输出是?
    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);

编译器的贪心法
  • 编译器以从左向右的顺序,一个一个地尽可能多的读入字符
  • 当读入的字符不可能和已经读入的字符组成合法符号为止
代码分析:
int j = ++i+++i+++i; // 编译器先读到1个'+',不知道啥意思; // 继续读到1个'+',它觉得这是个前置的'++' // 然后读到了i,确定了这是个'++i'表达式 // 继续读到1个'+',这可能是一个加法的运算符 '+' // 继续读到1个'+',这时候判断是一个后置的 '++',后面再读任何数都不对了,读到变量,不合法;读到符号,也不对; // 这时候编译器就停止处理了,所以编译器就得到了 “++i++” // 然后计算得到了 “1++”,对一个右值1进行自增,自然会编译错误!int c = a+++b; // 编译器依次读了3个字符'a++',知道这是个后置的++ // 然后读到了1个'+',觉得这个可能是个加法运算符 // 继续读到了b,后面也没有其他字符了,所以得到了 (a++) + b // 计算得到了c = 5b = b/*p; // error,编译器会将/*识别为注释,因此整个程序会编译失败 // 如果愿意是用b的值除以*p的值,那么就应该用括号或者空格表明 b = b / (*p); 或 b = b / *p;

【【C进阶】17、++和--操作符分析】本文总结自“狄泰软件学院”唐佐林老师《C语言进阶课程》。
如有错漏之处,恳请指正。

    推荐阅读