这是未定义行为 (undefined behavior) (UB),
C 标准没有去规范,编译器可以自行演绎,所以无法确认结果。
这种做法是不正确的,即使能够成功编译。
同一段出现两次副作用。 计算机编程中经常提到的副作用,具体指的是什么?有什么定义吗? - 知乎
C 标准没有指定哪一边先开始求值。(C++ 17 有)
所以在不同编译器上出現的結果不相同,未定义行为。
___
i = 1; i = ++i + ++i;
gcc 编译器:
++i; ++i; i = i + i; // ---> 6
clang 编译器:
a = ++i; b = ++i; i = a + b; // ---> 5
某编译器,進行公共子表达式消除:
i = (++i) * 2; // or i = (++i) << 1; // ---> 4
所以 (++i)+(++i)+(++i) = 10 是有可能的,但你真的很难预计编译器在背后做了什么。
你可以用反汇编去了解,但在未反汇编之前,你无法估计它会做什么。所以,未定义行为。
另外,上面这个例子是在 求值过程中的副作用与顺序点 这里拿下来的,挺好、挺有趣的文章。
我打算每次出现这种问题都贴这篇 ......
然而:https://www.zhihu.com/pin/1184823082127200256
___
最后,有趣的是,在 C++ 17,并不是 UB 了。
In C++17 an extra sentence is added to the specification of assignment operator:
The right operand is sequenced before the left operand.
但即使行为有定义了,也不建议你这样写,这只会增加日后的维护难度。
(根据 @黄亮anthony 的补充,本题在C++17也还是ub,+左右的sequence point还是未定义。只有赋值相关的才有定义)
很详细。
更更更详细具体的部分请直接看标准文档,但相信已经会看那些的大神不需要我说。 ╮(╯_╰)╭
___
除了点赞,不会再更新专栏/其他东西。
___
出这种题的都该挨揍,在实际工程中写成这样就更该挨揍了。回答完毕。
------------更新 好吧, 认真答一下------------
写个c程序:
#include <stdio.h> int main(void) { int i = 1; int n = (++i) + (++i); printf("%d
", n); return 0; }
然后执行gcc 21.c && a, 确实结果是6.
现在看一下汇编代码, 先不开优化, 直接gcc -masm=intel -O0 -S 21.c, 看一下汇编出来的结果:
.file "21.c" .intel_syntax noprefix .def ___main; .scl 2; .type 32; .endef .section .rdata,"dr" LC0: .ascii "%d12 " .text .globl _main .def _main; .scl 2; .type 32; .endef _main: push ebp mov ebp, esp and esp, -16 sub esp, 32 call ___main mov DWORD PTR [esp+28], 1 // 这里是i=1 add DWORD PTR [esp+28], 1 // ++i add DWORD PTR [esp+28], 1 // 又是++i mov eax, DWORD PTR [esp+28] // i进eax add eax, eax // 这次是i=i+i mov DWORD PTR [esp+24], eax mov eax, DWORD PTR [esp+24] mov DWORD PTR [esp+4], eax mov DWORD PTR [esp], OFFSET FLAT:LC0 call _printf mov eax, 0 leave ret .ident "GCC: (tdm-1) 5.1.0" .def _printf; .scl 2; .type 32; .endef
确实是按字面上来, 先执行两次++, 再执行括号外的+.
如果开优化呢? 再试一次:
push ebp mov ebp, esp and esp, -16 sub esp, 16 call ___main mov DWORD PTR [esp+4], 6 mov DWORD PTR [esp], OFFSET FLAT:LC0 call _printf xor eax, eax leave ret
好吧, 因为都是常数, 编译器直接给算好了.
要想不让编译器帮你算, 前面不能给i直接赋值为1. 改成用scanf接受好了. 先不开优化, 再试,
开优化, 则还是6.
再看汇编代码, 只看关键部分:
mov eax, DWORD PTR [esp+24] add eax, 1 mov DWORD PTR [esp+24], eax mov edx, DWORD PTR [esp+24] mov eax, DWORD PTR [esp+24] add eax, 1 mov DWORD PTR [esp+24], eax mov eax, DWORD PTR [esp+24] add eax, edx mov DWORD PTR [esp+28], eax mov eax, DWORD PTR [esp+28]
把i的值读到eax, 然后在eax里+1, 再放回去, 再放到edx; 再把i的值读到eax, 注意这时i的值是2了. 再+1, 再放回去, 已经是3了. 再和之前edx那个(还是2)相加, 结果就成5了.
如果开优化-Os呢?
mov eax, DWORD PTR [esp+28] add eax, 2 mov DWORD PTR [esp+28], eax add eax, eax
把i的值放到eax, 然后直接帮你+2, 再放回去(其实没有必要, 但是程序不知道i的值已经没用了.) 然后在eax里再加自己, 于是还是3+3=6.
以上是bug吗?再用arm-none-eabi-gcc试一下,先不开优化:
ldr r3, [fp, #-12] add r3, r3, #1 str r3, [fp, #-12] ldr r2, [fp, #-12] ldr r3, [fp, #-12] add r3, r3, #1 str r3, [fp, #-12] ldr r3, [fp, #-12] add r3, r2, r3
从i到r3, +1, 再放回i, 这时i的值是2. 再从i到r2一份(还是2), 再到r3, +1, 放回i. 最后r3=r2+r3, 那不还是5吗?
开优化-Os:
ldr r1, [sp, #4] add r1, r1, #2 str r1, [sp, #4] mov r1, r1, asl #1
从i到r1, 然后一样是给+2了, 再放回i. 然后直接把r1里的左移1位也就是*2了... 所以还是6.
gcc版本5.1.0, tdm-1, arm-none-eabi-gcc版本4.9.3.
其他平台上的情况就不知道了, 哪位有兴趣了自己试试.
----------最后再补充一下--------------
这个程序如果加了-Wall, 编译时就会告诉你:
21.c: In function 'main': 21.c:7:22: warning: operation on 'i' may be undefined [-Wsequence-point] int n = (++i) + (++i);
所以一定要养成加-Wall的好习惯.
来人,上《编译原理》……你爱等于几等于几
这种问题就好像我老婆她们别的专业开的计算机课……我都想把她们老师拖出来打死!
出这种UB(Undefined Behaviour)题目很无聊,这个我已经多次说过了。在实际工作当中写这种代码的首先就应该拉出去毙了。
无论是4,还是5,还是6,都是错的。因为这是个UB,没有标准答案。
但是为什么我还是来答这种问题,因为我认识到这种问题被频频拿出来考新手,是因为新手很容易在这里翻车。而研究新手为什么很容易在这里翻车,我觉得是有意义的。
所以本回答旨在探讨新手为什么容易在这里翻车,思维的陷阱到底在哪里。(本回答并不是为了解释这个UB的成因本身,本回答其实也不太适合对计算机组成原理不熟悉的同学,当然你要看我也没办法,但是包教不包会,据此面试后果自负)
++i,如果单独写成一个语句,等同于i=i+1。我想这个应该没有人有问题。
但是其实这里应该是有问题的,特别是新手。对于新手来说,如果没有意识到这里的问题,那么就很可能在上面那个题目当中翻车。
什么问题呢?
i=i+1,这个式子,在数学上是不成立的。或者说是无解的。(为防止被数学大佬喷,限定一下在初等数学范围内。。。)
也就是说,程序当中的表达式,并不是完全等同于数学公式。因此,不能以代数方程的理念去理解它。
程序当中的i=i+1,其实应该理解为:从一个名叫i的地方(内存空间)取出一个数,将其加1,然后放入名叫i的地方。
也就是,程序当中的表达式,其实只是一系列语句(statement)的简写。i=i+1写成完整语句,其实是:
所以,(++i)+(++i)的意思其实是:
(但是注意虽然上面编了序号,但是1-3和4-6之间其实是并列关系,并非要按照序号顺序执行。7和8也是并列关系,可以按任何顺序执行。而这种执行的顺就是导致这个问题是个UB问题的原因)
这样就应该没有人还是认为结果一定是4了吧。
新手掉坑里的原因就在于,把程序当中的表达式当作数学式子,把其中的字母当作未知数。
而理解表达式并不等同于数学式子,变量也不等同于未知数,在我看来是此类问题唯一的价值。
2001/02/19 补充:
技术上:光刻机的制造和使用工艺。
金融上:打破美元的霸权。
军事上:台湾问题。
别的问题(房产绑架经济、老龄化、东西部平衡等)都是发展中的问题,都是可能通过发展来解决的。
上面列的3个问题是当前面临的核心问题。
选南大,因为我就是南大毕业,南大开拓了我的眼界,丰富了我的学识,是我梦想起飞的地方,希望有一天南大以我为荣!
选南大,因为我就是南大毕业,南大开拓了我的眼界,丰富了我的学识,是我梦想起飞的地方,希望有一天南大以我为荣!
选南大,因为我就是南大毕业,南大开拓了我的眼界,丰富了我的学识,是我梦想起飞的地方,希望有一天南大以我为荣!