红联Linux门户
Linux帮助

关于a = (++a)+(++a)+(++a)+(++a);执行结果的疑问

发布时间:2010-05-14 22:32:10来源:红联作者:naruto01
在《Linux C编程一站式学习》一书中讲到,a = (++a)+(++a)+(++a)+(++a);的结果是 Undefined。
gcc测试下,a初始化为1,结果输出为15。
虽然知道这4个++a的执行前后是不能确定的,只能依据具体的编译器。
可想不通的地方:肯定要执行这4个++a,可顺序不同对于这个表达式来说,并不影响其结果吧。也就是说可以写成a = 2 + 3 + 4 + 5;(顺序随意),也就是说a的值是14。
请问多出的那个1是哪来的?
文章评论

共有 29 条评论

  1. naruto01 于 2010-05-18 20:45:24发表:

    28# 绿色圣光
    你们还能用电脑室 - -# 悲情色彩的我已经半年没权利进学校那地方了。。
    在进去就学EDA了,阿弥陀佛。

  2. naruto01 于 2010-05-18 20:37:56发表:

    [i=s] 本帖最后由 naruto01 于 2010-5-18 20:42 编辑 [/i]

    28# 绿色圣光
    Undefined类的问题,很正常。
    附(摘自Linux C编程一站式学习):
    Implementation-defined、Unspecified和Undefined
    在C 标准中没有做明确规定的地方会用Implementation-defined、Unspecified或Undefined来表述,在本书中有时把这三种情况统称为“未明确定义” 的。这三种情况到底有什么不同呢?
    Implementation-defined的情况,C标准没有明确规定char是有符号的还是无符号的,但是要求编译器必须对此做出明确规定,并写在编译器的文档中。
    Unspecified的情况,往往有几种可选的处理方式,C标准没有明确规定按哪种方式处理,编译器可以自己决定,并且也不必写在编译器的文档中,这样即便用同一个编译器的不同版本来编译也可能得到不同的结果,因为编译器没有在文档中明确写它会怎么处理,那么不同版本的编译器就可以选择不同的处理方式,比如下一章我们会讲到一个函数调用的各个实参表达式按什么顺序求值是Unspecified的。
    Undefined的情况则是完全不确定的,C标准没规定怎么处理,编译器很可能也没规定,甚至也没做出错处理,有很多Undefined的情况编译器是检查不出来的,最终会导致运行时错误,比如数组访问越界就是Undefined的。

  3. 绿色圣光 于 2010-05-18 19:02:11发表:

    锟斤拷锟斤拷:
    24# 6shao
    ++优先级的确要比+高,不过请注意有四个++,更何况还有(),- -# 尽管还有别的原因。
    源代码:#include

    int main(void)
    {
    int a = 1;
    printf("%d\n",(++a)+(++a)+(++a)+(++a)) ...
    naruto01 发表于 2010-5-18 10:28

    今天在学校实验室的电脑上用vc6.0编译了一下,结果也是一样的,(++a)+(++a) 得出的结果为6。
    而在ARM上的结果则不一样,(++a)+(++a) 得到的结果为5。如果用Java写这个,结果也是5。

  4. hantu 于 2010-05-18 14:41:20发表:

    现实中可不要这么编程,不然可能没有预期的编译和运行结果。

  5. naruto01 于 2010-05-18 10:28:05发表:

    [i=s] 本帖最后由 naruto01 于 2010-5-18 10:34 编辑 [/i]

    24# 6shao
    ++优先级的确要比+高,不过请注意有四个++,更何况还有(),- -# 尽管还有别的原因。
    源代码:[code]#include

    int main(void)
    {
    int a = 1;
    printf("%d\n",(++a)+(++a)+(++a)+(++a));
    a = 1;
    printf("%d\n",(++a)+0+(++a)+0+(++a)+0+(++a));
    a = 1;
    printf("%d\n",(++a)+1+(++a)+1+(++a)+1+(++a));
    return 0;
    }[/code]反汇编代码:[code] int a = 1;
    80483ed: c7 44 24 1c 01 00 00 movl $0x1,0x1c(%esp)
    80483f4: 00
    printf("%d\n",(++a)+(++a)+(++a)+(++a));
    80483f5: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    80483fa: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    80483ff: 8b 44 24 1c mov 0x1c(%esp),%eax
    8048403: 01 c0 add %eax,%eax
    8048405: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    804840a: 03 44 24 1c add 0x1c(%esp),%eax
    804840e: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    8048413: 89 c2 mov %eax,%edx
    8048415: 03 54 24 1c add 0x1c(%esp),%edx
    8048419: b8 80 85 04 08 mov $0x8048580,%eax
    804841e: 89 54 24 04 mov %edx,0x4(%esp)
    8048422: 89 04 24 mov %eax,(%esp)
    8048425: e8 f2 fe ff ff call 804831c
    a = 1;
    804842a: c7 44 24 1c 01 00 00 movl $0x1,0x1c(%esp)
    8048431: 00
    printf("%d\n",(++a)+0+(++a)+0+(++a)+0+(++a));
    8048432: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    8048437: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    804843c: 8b 44 24 1c mov 0x1c(%esp),%eax
    8048440: 01 c0 add %eax,%eax
    8048442: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    8048447: 03 44 24 1c add 0x1c(%esp),%eax
    804844b: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    8048450: 89 c2 mov %eax,%edx
    8048452: 03 54 24 1c add 0x1c(%esp),%edx
    8048456: b8 80 85 04 08 mov $0x8048580,%eax
    804845b: 89 54 24 04 mov %edx,0x4(%esp)
    804845f: 89 04 24 mov %eax,(%esp)
    8048462: e8 b5 fe ff ff call 804831c
    a = 1;
    8048467: c7 44 24 1c 01 00 00 movl $0x1,0x1c(%esp)
    804846e: 00
    printf("%d\n",(++a)+1+(++a)+1+(++a)+1+(++a));
    804846f: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    8048474: 8b 44 24 1c mov 0x1c(%esp),%eax
    8048478: 83 c0 01 add $0x1,%eax
    804847b: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    8048480: 03 44 24 1c add 0x1c(%esp),%eax
    8048484: 83 c0 01 add $0x1,%eax
    8048487: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    804848c: 03 44 24 1c add 0x1c(%esp),%eax
    8048490: 83 c0 01 add $0x1,%eax
    8048493: 83 44 24 1c 01 addl $0x1,0x1c(%esp)
    8048498: 89 c2 mov %eax,%edx
    804849a: 03 54 24 1c add 0x1c(%esp),%edx
    804849e: b8 80 85 04 08 mov $0x8048580,%eax
    80484a3: 89 54 24 04 mov %edx,0x4(%esp)
    80484a7: 89 04 24 mov %eax,(%esp)
    80484aa: e8 6d fe ff ff call 804831c [/code]可以看出 第一次输出和第二次输出是等价的。
    第三次也符合我的推断。
    执行结果是
    15
    15
    17
    这个解释只能在X86 + GCC的条件下貌似成立(因为我没有什么绝对的证据来证明),对于这个Undefined问题,的确没有多大必要讨论,因为仁者见仁,智者见智,所以关于这个讨论可以结束了。
    重要的是在这个过程中,我通过学习,练习了gdb调试,反汇编等。哈哈~小小高兴一下,继续看书去。。

    ps:那个反汇编的格式搞定了。。- -# 原来我脑残了

  6. 6shao 于 2010-05-18 08:54:39发表:

    15# alick
    平时实际操作中当然不能使用这种语句,不过平时无聊时可以试试,对编译器也是一种了解不是吗?

  7. 6shao 于 2010-05-18 08:52:33发表:

    8# naruto01
    个人以为是优先级问题,++的优先级要高于+,所以其中一个(++a)+(++a)会先执行两个++a,即3+3,然后再依次执行。这就是为什么实际得到的结果会比预期的多1。可是问题又来了,如果在++a之间加入常量会不会导致结果往预期的方向发展呢?我尝试了a=(++a)+0+(++a)+0+(++a)+0+(++a)和a=(++a)+1+(++a)+1+(++a)+1+(++a),可是结果分别为15和18,并未向预期的方向发展。

  8. prinse 于 2010-05-18 07:37:42发表:

    根据 C/C++ 规范,这样的表达式是所谓的“未定义行为表达式”,意思就是说,它的值是“不可确定的”……

  9. tan_tan_1 于 2010-05-17 18:03:57发表:

    在学校学习的时候钻一钻这种牛角尖没什么,但是在实际工作中我觉得这个问题没有太大的意义,我可不希望看到这种代码,在工作中首先要考虑的是稳定,我要确切的知道执行结果,要是执行结果还要人费劲去猜,那明天老板可能就叫我走人了

  10. liuk1989 于 2010-05-16 02:31:17发表:

    [i=s] 本帖最后由 liuk1989 于 2010-5-16 02:34 编辑 [/i]

    解答好复杂啊。是不是可以这样理解
    gcc的编译:每次加法只有两个参与,加后结果要送到另一个暂时的空间。

  11. 太阳之光 于 2010-05-15 22:07:20发表:

    一切的一切都是根据右结合性!

  12. 相思爱文 于 2010-05-15 21:02:37发表:

    好像一些编程技巧要用汇编语言才能实现。
    高级语言需要清晰,其次才是效率。

  13. 绿色圣光 于 2010-05-15 14:30:47发表:

    锟斤拷锟斤拷:
    8# naruto01
    “C语言变量操作以指针实现。”不敢苟同,我以为应该以汇编的寄存器概念来解释这个问题,而不是指针。指针是地址对应的应该是mem的空间,但显然程序执行的时候是寄存器在参与运算。
    我想这个问题之所 ...
    glxin1982 发表于 2010-5-15 10:50

    貌似有点明白了……

  14. naruto01 于 2010-05-15 13:45:02发表:

    14# iamxzg
    强烈认可!

  15. naruto01 于 2010-05-15 13:42:12发表:

    15# alick
    呵呵~
    的确这仅仅纯属钻牛角尖的问题,这本书里开章也预先说了。悲剧的是遇到了我这样喜欢钻牛角尖的人,所以就偶尔钻钻,磨练自己的耐性,更健康点。

  16. alick 于 2010-05-15 13:21:27发表:

    这种代码就不是让人看到,更不是让人用的。
    不同编译器会给出不同的结果。同一种编译器也不能保证是同一结果,
    所以才说结果是UNDEFINDED。
    当年一些所谓考试会出类似这样的题目为难人,现在
    再出这样的题目,会被严重鄙视的。
    所以不要去理会这样的代码了

  17. iamxzg 于 2010-05-15 12:43:23发表:

    8楼厉害。但我觉得这样的代码可读性不好,而且容易犯错(换到不同编译器上得到的结果可能不能),我不建议这么去做。

  18. jhw0813 于 2010-05-15 12:14:03发表:

    我的妈呀!
    看不懂!
    路过!

  19. naruto01 于 2010-05-15 11:57:35发表:

    [i=s] 本帖最后由 naruto01 于 2010-5-15 12:01 编辑 [/i]

    11# glxin1982
    反汇编的确很容易就可以解决这个疑问。 - -# 俺汇编水平也就了解而已,能力有限,只能靠猜测了。
    Linux下的反汇编还不知道咋搞,Windows下也懒得找工具了。(阿弥陀佛~我有罪)
    谢谢你提供的代码,很清楚。非常感谢~

  20. glxin1982 于 2010-05-15 10:50:11发表:

    [i=s] 本帖最后由 glxin1982 于 2010-5-15 11:05 编辑 [/i]

    8# naruto01
    “C语言变量操作以指针实现。”不敢苟同,我以为应该以汇编的寄存器概念来解释这个问题,而不是指针。指针是地址对应的应该是mem的空间,但显然程序执行的时候是寄存器在参与运算。
    我想这个问题之所以造成困惑,一方面是编译器怎么处理中几个++a顺序,第二个是参与加法的操作数到底是什么。但如果把它们翻译成汇编语言(当然要遵从编译器关于++a运算顺序的规定)第二方面就一目了然。举个例子:(++a)+(++a)*(++a)*(++a)
    r0表示a,r1是一个存放临时变量的寄存器。
    mov r0 #1
    add r0 r0 #1
    add r0 r0 #1
    add r0 r0#1
    mult r1 r0 r0
    add r0 r0 #1
    mult r1 r1 r0
    add r0 r1 r0
    俺使用的是一种很小众的汇编(大众的不会)。不知道能不能看懂。

  21. wangyu 于 2010-05-15 08:47:59发表:

    还好我不学编程

  22. dchwlinux 于 2010-05-15 07:16:08发表:

    我啥也不说了,楼上正解

  23. naruto01 于 2010-05-15 02:12:43发表:

    [i=s] 本帖最后由 naruto01 于 2010-5-15 11:55 编辑 [/i]

    #include

    int main(void)
    {
    int a = 1;
    printf("%d\n",(++a)+(++a));
    a = 1;
    printf("%d\n",(++a)+(++a)+(++a)+(++a));
    a = 1;
    printf("%d\n",((++a)+(++a))+((++a)+(++a)));
    a = 1;
    printf("%d\n",(++a)+(++a)*(++a)+(++a)); /* 应该理解是4+4*4+5 */
    a = 1;
    printf("%d\n",(++a)+(++a)*(++a)*(++a)); /* 应该理解是5+4*4*5 */
    return 0;
    }
    输出为:
    6
    15
    16
    25
    85

    自己猜想下:C语言变量操作以指针实现。(++a)+(++a),第一个(++a)之后,a的值变为2,参与在加法操作的是指针p,第二个(++a)后,a的值变为3,参与操作的是指针p,*p+*p。也就是3+3了。
    3+3已经只能做右值,所以,后面依次加4,加5。
    这是对3L的理解。

    (++a)+(++a)*(++a)+(++a)的猜测:跟上面同理,只不过加上了个*,因为*高于+,所以第一个(++a)一直在等,直到可以加一个右值。

    我在想会不会因为对称导致上面的猜想看起来挺完美,所以我又尝试了(++a)+(++a)*(++a)*(++a),发现跟猜想的一样。

    总结起来就是:在gcc下,(++a)的走向是从左到右的,每一个(++a)直到遇见可以做右值的表达式停下,进行计算。而这过程是以指针来控制的。

  24. naruto01 于 2010-05-15 01:02:37发表:

    6# dchwlinux
    请教单独算(++a)+(++a)。

  25. dchwlinux 于 2010-05-15 00:47:55发表:

    楼上正解。这种式子顺序是不能乱的,必须一步步来。没有所谓的先进行4个++a,那式子最后执行四个a(=5)相加就成20了。这个式子最关键的就是这个(++a)+(++a),单独算这个式子你会吧。你感兴趣的话可以试着算算a=(++a)+(++a)*(++a)+(++a)

  26. naruto01 于 2010-05-15 00:35:41发表:

    3# vitacy

    第五行很诡异。

  27. naruto01 于 2010-05-15 00:35:24发表:

    2# 绿色圣光

    怎么跟踪出来的?

  28. vitacy 于 2010-05-14 23:51:06发表:

    (++a)+(++a)+(++a)+(++a);
    (1 ) (2) (3 ) (4)
    ++a;//a=2 (1 )
    ++a;//a=3 (2)
    (1 ) +(2) = 3+3=6
    ++a;//a=4 (3 )
    6+4=10
    ++a;//a=5(4)
    10+5=15

  29. 绿色圣光 于 2010-05-14 23:47:15发表:

    奇怪,(++a)+(++a)变成了3+3
    研究中……