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

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 语言中的一个完整符号的休止符,编译器读入空格后立即对之前读入的符号进行处理

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页