c += c-- | ++b;

时间:2023-03-10 04:11:39
c += c-- | ++b;

一切都是从这开始的

一个大一学弟通过QQ给我发来一个C++的题:

int c = 8, b = 3;
c += c-- | ++b;

问c的值是多少。通过笔算得到c为19,然后随手建了个C#控制台项目跑了一下,悲剧了。。。C#输出的为20。重新笔算一遍还是19啊,赶紧重新建了一个C++控制台项目跑出的结果为19。到底为什么C++和C#会不一样呢?

求证1

通过网上查资料得知,是C#求值顺序的问题,具体是怎么样的情况呢?我们来反汇编一下:

	.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// Code size 33 (0x21)
.maxstack 4
.locals init ([0] int32 c,
[1] int32 b)
IL_0000: nop
IL_0001: ldc.i4.8
IL_0002: stloc.0
IL_0003: ldc.i4.3
IL_0004: stloc.1
IL_0005: ldloc.0
IL_0006: ldloc.0
IL_0007: dup
IL_0008: ldc.i4.1
IL_0009: sub
IL_000a: stloc.0
IL_000b: ldloc.1
IL_000c: ldc.i4.1
IL_000d: add
IL_000e: dup
IL_000f: stloc.1
IL_0010: or
IL_0011: add
IL_0012: stloc.0
IL_0013: ldloc.0
IL_0014: call void [mscorlib]System.Console::WriteLine(int32)
IL_0019: nop
IL_001a: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_001f: pop
IL_0020: ret
} // end of method Program::Main

通过IL代码我们可以清晰的看到他的计算过程:

  • 0~4行 为变量赋值Call Stack里0为变量c值为8,1为变量b值为3
  • 5行 缓存了c的值,放到了Evaluation Stack里,缓存的值为8
  • 6行 缓存了一个c值,缓存的值为8
  • 7~a行 执行了c=c-1,此时c值为7
  • b~f行 执行了b=b+1,并留了一个副本,值为4
  • 10行 6行缓存的数8和b~f行中留的4做or,值为12
  • 11行10行中结果12再加5行中缓存的8结果为20
  • 12行存储到变量c中

c += c-- | ++b; 等价于 c = c + (c-- | ++b);,通过反汇编我们可以看出:

  1. C#的求值顺序为从左到右不会因为运算顺序改变,等号右侧第一个c的值在一开始就缓存了。
  2. c--在求值之后立刻就进行了结算,c变量此时值变为了7,但是c变量的值并不影响算式最终的结果,原因见1。
  3. 把算式变为c = c + (--c | ++b);得到的值为15(8+(7|4)),反汇编后观察,结论同第一条,c--和--c只影响了括号中的运算结果。

根据上面结论我们把算式改成c = (c-- | ++b) + c;,得到的结果为19。

求证2

那么C++到底是怎么执行的呢?继续,反编译之:

c += c-- | ++b;

(上图反汇编的程序基于VS2013的C++ Debug编译结果,GCC 4.6.1的反汇编代码略有区别,执行过程一致,结论仅限定在这两个编译环境下)

C++的执行过程:

  1. 使用eax寄存器做b=b+1
  2. 使用ecx寄存器做c=8+(8|4)(此时b=4)
  3. 使用edx寄存器做c=c-1

从反汇编可以看出:

  • C++不会缓存数值,C++也没有规定求值顺序。
  • C++在计算的时候才取值,所以c = (c-- | ++b) + c;的结果还是19。
  • C++是在整个算式结束的时候才进行的c--,也就是说之所以结果是19不是20,不是因为先算括号中的or造成最后的加法中的c为7,而是因为c--是在算式赋值结束后才进行结算。(老师们,你们教对了么?)

最后验证一下,改变算式为c = c * 2 + (c-- | ++b);C++的输出结果为27,8*2+12=28-1=27,反汇编也可以看到最后才执行的sub,图就不上了。

最后

  • 如果你在学C++,千万不要用C#来验证你的作业题答案。。。
  • 就让‘++’这种东西只出现在for语句中吧。。。

随机推荐

  1. opencv中Mat与IplImage,CVMat类型之间转换

    opencv中对图像的处理是最基本的操作,一般的图像类型为IplImage类型,但是当我们对图像进行处理的时候,多数都是对像素矩阵进行处理,所以这三个类型之间的转换会对我们的工作带来便利. Mat类型 ...

  2. TypeScript: Angular 2 的秘密武器(译)

    本文整理自Dan Wahlin在ng-conf上的talk.原视频地址: https://www.youtube.com/watch?v=e3djIqAGqZo 开场白 开场白主要分为三部分: 感谢了 ...

  3. 数据库优化案例——————某市中心医院HIS系统

    记得在自己学习数据库知识的时候特别喜欢看案例,因为优化的手段是容易掌握的,但是整体的优化思想是很难学会的.这也是为什么自己特别喜欢看案例,今天也开始分享自己做的优化案例. 最近一直很忙,博客产出也少的 ...

  4. TODO:macOS上ThinkPHP5和Semantic-UI集成

    TODO:macOS上ThinkPHP5和Semantic-UI集成 1. 全局安装 (on OSX via homebrew)Composer 是 homebrew-php 项目的一部分 2. 把X ...

  5. [APUE]文件和目录(中)

    一.link.unlink.remove和rename 一个文件可以有多个目录项指向其i节点.使用link函数可以创建一个指向现存文件连接 #include <unistd.h> int ...

  6. Objective-C中block的底层原理

    先出2个考题: 1. 上面打印的是几,captureNum2 出去作用域后是否被销毁?为什么? 同样类型的题目: 问:打印的数字为多少? 有人会回答:mutArray是captureObject方法的 ...

  7. android绘制圆形图片的两种方式

    看下效果先 下面有完整的示例代码 使用BitmapShader(着色器) 我们在绘制view 的时候 就是小学上美术课 用水彩笔在本子上画画 使用着色器绘制圆形图片最简单的理解方式 就是把bitmap ...

  8. Android6.0运行时权限管理

    自从Android6.0发布以来,在权限上做出了很大的变动,不再是之前的只要在manifest设置就可以任意获取权限,而是更加的注重用户的隐私和体验,不会再强迫用户因拒绝不该拥有的权限而导致的无法安装 ...

  9. SQLServer2005创建定时作业任务

    SQLServer定时作业任务:即数据库自动按照定时执行的作业任务,具有周期性不需要人工干预的特点 创建步骤:(使用最高权限的账户登录--sa) 一.启动SQL Server代理(SQL Server ...

  10. 使用四元数解决万向节锁(Gimbal Lock)问题

    问题 使用四元数可以解决万向节锁的问题,但是我在实际使用中出现问题:我设计了一个程序,显示一个三维物体,用户可以输入绕zyx三个轴进行旋转的指令,物体进行相应的转动. 由于用户输入的是绕三个轴旋转的角 ...