百科问答小站 logo
百科问答小站 font logo



i=1,为什么 (++i)+(++i)=6? 第1页

  

user avatar   CWKSC 网友的相关建议: 
      

这是未定义行为 (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 是有可能的,但你真的很难预计编译器在背后做了什么。

你可以用反汇编去了解,但在未反汇编之前,你无法估计它会做什么。所以,未定义行为。

另外,上面这个例子是在 求值过程中的副作用与顺序点 这里拿下来的,挺好、挺有趣的文章。

重复出现了很多次的这种问题,下面问题的答案应该能够解答:

我打算每次出现这种问题都贴这篇 ......

然而:zhihu.com/pin/118482308

___

最后,有趣的是,在 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++ 文档:

很详细。

更更更详细具体的部分请直接看标准文档,但相信已经会看那些的大神不需要我说。 ╮(╯_╰)╭



___

除了点赞,不会再更新专栏/其他东西。

___


user avatar   zhang-hao-72 网友的相关建议: 
      

出这种题的都该挨揍,在实际工程中写成这样就更该挨揍了。回答完毕。

------------更新 好吧, 认真答一下------------

写个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接受好了. 先不开优化, 再试,

注意注意!!!!!!!!!!!! 这次运行的结果变成5了!!!!!!!!!!!!!!!

开优化, 则还是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的好习惯.


user avatar   mai-cui-ya-96 网友的相关建议: 
      

来人,上《编译原理》……你爱等于几等于几

这种问题就好像我老婆她们别的专业开的计算机课……我都想把她们老师拖出来打死!


user avatar   netwarm007 网友的相关建议: 
      

出这种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写成完整语句,其实是:

  1. load i -> 某CPU寄存器
  2. 将该寄存器内容+1
  3. save该寄存器内容 -> i

所以,(++i)+(++i)的意思其实是:

  1. load i -> 某CPU寄存器 (第一个括号
  2. 将该寄存器内容+1
  3. save该寄存器内容 -> i
  4. load i -> 某CPU寄存器 (第二个括号
  5. 将该寄存器内容+1
  6. save该寄存器内容 -> i
  7. load i -> 某CPU寄存器 (两个括号之间
  8. load i -> 另一个CPU寄存器
  9. 将两个寄存器内容相加

(但是注意虽然上面编了序号,但是1-3和4-6之间其实是并列关系,并非要按照序号顺序执行。7和8也是并列关系,可以按任何顺序执行。而这种执行的顺就是导致这个问题是个UB问题的原因)

这样就应该没有人还是认为结果一定是4了吧。

新手掉坑里的原因就在于,把程序当中的表达式当作数学式子,把其中的字母当作未知数。

而理解表达式并不等同于数学式子,变量也不等同于未知数,在我看来是此类问题唯一的价值。

2001/02/19 补充:


user avatar   damon-dance-for-me 网友的相关建议: 
      

技术上:光刻机的制造和使用工艺。

金融上:打破美元的霸权。

军事上:台湾问题。


别的问题(房产绑架经济、老龄化、东西部平衡等)都是发展中的问题,都是可能通过发展来解决的。

上面列的3个问题是当前面临的核心问题。


user avatar   xinxiweiyuan 网友的相关建议: 
      

选南大,因为我就是南大毕业,南大开拓了我的眼界,丰富了我的学识,是我梦想起飞的地方,希望有一天南大以我为荣!


user avatar   lilydjwg 网友的相关建议: 
      

选南大,因为我就是南大毕业,南大开拓了我的眼界,丰富了我的学识,是我梦想起飞的地方,希望有一天南大以我为荣!


user avatar   farta-fine 网友的相关建议: 
      

选南大,因为我就是南大毕业,南大开拓了我的眼界,丰富了我的学识,是我梦想起飞的地方,希望有一天南大以我为荣!




  

相关话题

  有没有什么程序库使得我们可以比较方便的在windows下使用比较新版本的opengl的? 
  个人或者小团队选择C语言还是c++? 
  既然scanf和strcpy等函数会被编译器报不安全,那么C语言教材为什么还讲这些函数? 
  C++工程中的目录有何意义? 
  C的结构体成员变量的命名有必要加前缀吗? 
  C++的核心究竟是什么?学到什么程度才算精通? 
  为什么C语言中主函数main后面有个()? 
  有什么C可以实现但C++不能实现的东西吗? 
  内存为啥要分堆栈在编程里,要是全部只用堆或者全部只用栈,行不行? 
  为什么 C++ 没有 C 语言快? 

前一个讨论
如何评价日本XF9-1发动机完成高空台最大加力推力实验?
下一个讨论
如何直观地感受光速?





© 2024-11-24 - tinynew.org. All Rights Reserved.
© 2024-11-24 - tinynew.org. 保留所有权利