搜索

首页  >  问答  >  正文

c++ - C语言 关于b=a+++a++运算问题?

篇幅有点长,让您受累了。。。

a++这个问题一直很困扰,自己做了个测试,虽然都知道a++是先使用

a再进行自加,疑问点就是这个a使用的期限是什么时候结束?一开始以为是表达式代码行完成后,在第二行代码需要试用a时,就是自加结果,这也是最常用的情况,也很好理解,如:

1

2

3

<code>int a=0,b;

b=a++;

printf("a=%d b=%d",a,b);//输出:a=1,b=0</code>

那假如在同一行表达式中出现2次a++,也就是说a++后又与其他变量进行运算,如下代码:

1

2

3

<code>int a=10,b=0;

b=a+++a++;

printf("a=%d b=%d",a,b);//输出a=12 b=21</code>

如果上面的理论成立那b应该等于20,根据执行结果显然不成立,所以我觉得应该是当执行a++运算时,a还是10,但当a在与其他变量继续运算时a就已经完成了自增,就是自增后的值与其他变量运算了,在变下代码再测试:

1

2

<code>int a=10,b=0;

b=a+++b;//b=10(因为a++优先级大于++b,所以直观点应该是b=(a++)+b,尽管此时括号是多余的)</code>

显然这种说法也不成立。

对b=a+++a++运算的猜测步骤为:

  1. 第一个a++ //此时a=10

  2. 第二个a++ //因为第一步运算完后a自增1,所以此时a=11,是第一个a++运算后的值

  3. b=a+a //b=11+11=22,这点就不理解了,之所以最终结果这个b=21,难道是b=10+11吗,但中间+号的表达式两端都是a,应该两端的值都是一样的啊,应是22或20啊,怎么会是21,b=(a++)+(++a) 这个结果为22,应该可以说明+号两边都是a的话,第一个表达式a++中a会被++a后的值覆盖,所以b=11+11。

求解释b=a++a++的详细运算步骤,为啥会是b=21?

还有个问题:

1

2

3

4

5

6

7

<code>int i=1;

int j=0;

for(;j<5;j++){

     i=i++;

     printf("i=%d",i);

}

printf("i=%d",i);</code>

为什么i=1?为什么i=i++执行完后,在执行j<5之前或在下一轮执行前i没有自加?及时for循环中i=1,那for循环执行完后i应该至少会加1吧,起码i也得等于2啊?

阿神阿神2809 天前2892

全部回复(17)我来回复

  • PHPz

    PHPz2017-04-17 14:02:29

    首先,其实楼上已经说的很明确了,写出这样的代码是很不负责任的行为。不过既然要探讨,那么就以探讨的方式来讨论这个问题吧。我测试的代码不复杂就下面这几行:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    <code class="c">#include <stdio.h>

    int main()

    {

        int a=0,b=0;

        a = 10;

        b = a+++a++;

        printf("%d %d\n", b, a);

        return 0;

    }</code>

    编译器:gcc version 4.9.2 (Raspbian 4.9.2-10)
    输出的结果:

    1

    <code>21 12</code>

    我们先讨论a+++a++的等价形势,根据C语言运算符优先级,后置++的优先级高于+的优先级,也就是a+++a++原则上可以等价于(a++)+(a++)
    那么,编译器是怎么理解的呢?它把它理解成语法树,类似这样:

    1

    2

    3

    4

    <code>        +

           / \

          /   \

        a++   a++</code>

    之后,编译器又怎么处理呢,基于栈操作的一般是这样的:

    • 1、处理第一个a++运算:因为是后置运算,先把a(此时a=10)入栈了,然后a=a+1,现在的a已经是11了;

    • 2、处理第二个a++运算:一样的,先把a(此时a=11)入栈了,然后a=a+1,现在的a已经是12了;

    • 3、处理+(和)运算:因为+属于二目操作运算(也就是需要两个操作数的运算),先从栈里面弹出两个操作数,即刚刚入栈的1011,再做+运算,即10+11,最后的结果就是21。

    基本描述就是:

    • push(a), a入栈(10)

    • a=a+1, a加1

    • push(a), a入栈(11)

    • a=a+1, a加1

    • b=pop()+pop(), 出栈两个操作数(10,11)相加,赋值到b(=21)

    在基于栈的处理方式中,操作数都是从栈弹出来再进行运算的(当然,栈里面的数据也是之前压进去的),而不是直接拿a去计算的,现代编译器基本都是基于栈方式实现的,我猜测你的疑惑估计就在这点上了。如果你想了解的话可以去看看编译原理方面的内容。

    按照编译器的一般做法应该就是上述步骤了,启用优化之后可能有些差异,看到有人说(a++)+(a++)在gcc4.5.1最终得到20,我手上没有4.5.1,但是在4.9.2和4.2.1上面都是得到21,而且不同优化级别都试了,如果属实那么可能是其他选项或者版本间问题。

    最后,编译原理我也没有深入学习过,只能说个大概,也不是所有的编译器都是采用栈方式来求解表达式,有错误的地方欢迎斧正。
    还是那句话,尽量写人容易理解的代码。

    回复
    0
  • 阿神

    阿神2017-04-17 14:02:29

    你拿不同的编译器去测试,会发现结果跟你现在的结果又不一样。或者你在同一个编译器下,改下编译参数,结果又不一样。归根结底这类问题是undefined behavior。所以,你要记住的不是i到底什么时候自加,而是保证自己不去写这种垃圾代码。。

    回复
    0
  • PHP中文网

    PHP中文网2017-04-17 14:02:29

    题主可以看一下这个
    [] (https://en.m.wikipedia.org/wiki/Undefined_behavior)
    在两个序列点(sequence point)之间对同一个对象进行两次修改是未定义行为,不可预测

    回复
    0
  • 天蓬老师

    天蓬老师2017-04-17 14:02:29

    standard C没有定义
    编译器根据自己的规则自己处理。

    回复
    0
  • 大家讲道理

    大家讲道理2017-04-17 14:02:29

    这个问题裘宗燕老师早已经给出了完整的回答。

    回到前面的例子:“谁知道下面C语句给n赋什么值?”

    m = 1; n = m++ +m++;

    正确回答是:不知道!语言没有规定它应该算出什么,结果完全依赖具体系统在具体上下文中的具体处理。其中牵涉到运算对象的求值顺序和变量修改的实现时刻问题。

    http://www.math.pku.edu.cn/teachers/qiuzy/technotes/expression2009.pdf

    回复
    0
  • 阿神

    阿神2017-04-17 14:02:29

    未定义行为。

    编译器实现不同是因为++是产生影响的,编译器消除影响的时间点不同。

    回复
    0
  • 天蓬老师

    天蓬老师2017-04-17 14:02:29

    这种问题没什么意义,你的代码中根本不能出现这种语句

    回复
    0
  • 迷茫

    迷茫2017-04-17 14:02:29

    我想起我以前看过的一篇文章,和你的问题有些类型.
    链接

    不管怎么样,都最好在项目中尽量使用简洁易懂的语句,不要出现这种不知道会出什么奇怪的问题的代码.

    回复
    0
  • 怪我咯

    怪我咯2017-04-17 14:02:29

    首先我很佩服题主的探索精神。
    但是我建议你去看一下:

    1. C语言运算符的优先级

    2. C语言运算符的结合性

    好像还不够,那再去看一下C语言规范中相关运算符的详细语意描述:

    http://eli-project.sourceforge.net/c_html/c.html

    好像还不行,那只能去看编译器的实现了。

    回复
    0
  • PHPz

    PHPz2017-04-17 14:02:29

    这个好像跟编译器是有关系的,其实理解时候你可以这样理解i++ => b = i; i = i + 1;然后返回值I++的返回值是b,这样理解就行了。有的编译器是返回i,然后 i = i + 1;

    回复
    0
  • 取消回复