深入解析序列点

   int i = 3;

   i = i++;

   cout << i;

   结果是什么?有人可能会说是3,也有人可能会说是4,更多的人在骂出题的人白痴,但这语句究竟有何问题呢?未必每个人都清楚。

   有些人也许马上会说,这是“未定义行为”。没错,这是一个典型的未定义行为。i = i++这个表达式合乎C++语法,能够顺利编译通过,但是执行的结果,标准说“未定义”。为什么是“未定义”,深究起来,要从序列点说起。

   序列点是程序中这样的一些点:通俗地说,执行至此,之前的语句都已经彻底执行干净执行完了,之后的语句还完全没开始执行;更常见、更严谨但略晦涩的说法是,之前的语句对现场环境的改变已经全部完成,之后的语句对现场环境的改变还没有开始。啥是现场环境呢?就是程序执行到某一点的那个状态,包括变量的内容、文件的内容等。

   这跟最开始那个例子有什么关系呢?关键的问题来了:标准规定,两个序列点之间,程序执行的顺序可以是任意的。没错,正如你猜的那样,C++标准规定一个完整的表达式结束之后有一个序列点,而例子中i = i++是位于两个序列点之间的。编译器可以先算完i++,再写结果给i,也可以先将i = i,再令i++.按前面的方法算,i先自增变为4,然后i++返回3,于是i被赋值为3;按后一种方法算,i先被赋值为3,随后自增变成4.标准说了,这两种处理方法,编译器你爱选那种就选哪种,随便。如果谁写的程序像这样依赖执行的顺序,让他自己哭去!

   等等,有人要问了,++的优先级难倒不是高于=吗?显然应该先执行++啊。这里有个概念的问题,前一段说的编译器先算i = i,绝不是说令=的优先级比++还高了。如果那样的话,表达式将变成 (i = i)++,也就是i.operator = (i)。 operator ++,执行++的主体变成i = i这个表达式的返回值了。上一段所说的先计算i = i,实际上还是先计算i++,只不过是先返回了i的值,然后推迟了将i自增1的操作先去干别的(i = i)去了,回头再来给i自增1.

   ——“什么,你说先干别的就先干别的,凭什么!”

   嗯,我再重复一遍,标准规定,两个序列点之间,程序执行的顺序可以是任意的。

   ——“不是吃饱了撑的嘛,标准搞这个干啥?严格按照顺序执行不就完了嘛”。

   C++标准弄这么复杂自然是有道理的。C++是极为重视执行效率的语言,这样做给了编译器优化的空间。比如考虑

   int j = i++;

   如果非得把i++执行干净了再干别的,那就不得不 temp = i; i += 1; j = i; .如果允许编译器打乱顺序执行呢,直接 j = i; i +=1; 就好了,省了一个temp倒一次的过程。

   多说一句,一些更高层的语言,不是像C++这种极为重视效率的,比如Java,上面的例子就完全没有问题。Java完全不允许你编译器乱搞,上面那个例子,在Java中一定是先把i++彻底执行干净了返回3,再进行赋值,赋值完之后不会再有别的操作了,所以结果一定是3.

   如何避免由序列点造成的这种未定义行为,有一句经典但有点晦涩的编程规则:“在相邻的两个序列点之间,一个对象只允许被修改一次,而且如果一个对象被修改则在这两个序列点之间只能为了确定该对象的新值而读一次”。其实明白了序列点具体是怎么回事,这个规则应该就很容易明白了。由于序列点之间程序执行顺序不确定,一个对象被修改多次的话最后留下的是哪次的结果就不确定。另外如果一个对象同时存在读取和修改,只有根据读取的结果来修改才是合法的,否则就会出现是先改完再读还是先读完再改的混乱。

   最后再说一下最新的C++2003标准中定义的序列点(详细说明请参考标准)

   ·完整声明之后

   ·完整表达式之后

   ·进入函数时与退出函数时

   ·|| && ?: , 四个操作符的第一个操作数之后

   最后一个似乎有点奇怪,为啥 + - 操作符之前就没有序列点,|| &&之前就有呢?a+b之间没有序列点而a||b之间就有,不公平啊。

   嗯,你猜的没错,是为了短路。

   不过要是手建重载了默认的||和&&,他们可就视同普通函数,不会在第一个操作数之后有序列点了,切记。

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
这是一本填补“使用向导”类的VisualC++书籍、产品文档以及MFC源代码之间空隙的MFC书籍。本书是了解MFC内幕的向导,提供了关于那些没有文档记录的MFC类、实用函数和数据成员的独一无二并且透彻的信息,介绍了有用的编码技巧,并对MFC各个类之间的协作方式进行了重要的分析。 本书的第一部分包含了核心的MFC图形用户界面类以及支持它们的类,第二部分包含了像OLE这种扩展基本Windows支持的主题。如果做到以下几,你就可以成为一位透彻理解MFC实现细节的专家:探索MFC文档/视图结构的内幕,从而学习视图同步、打印和打印预览;更深入地了解MFC序列化中那些没有文档记录的方面和一些没有文档记录的类,例如CPreview、CPreviewDC、CMirrorFile以及CDockBar等等;最后理解MFC 和OLE是如何共同运作的,以及OLE控悠扬是如何实现的;积累技巧,学会自己研究和理解MFC源代码。 目录: 前言 致谢 简介 第1章 MFC的概念性总括 面向对象编程的一些背景 面向对象编程术语 通常的对象 对象与C++ 为什么使用OOP 应用程序框架与MFC MFC要之旅 结语 第2章 基本的Windows支持 MFC与C/SDK 基本的MFC应用程序组件 现在,找到WinMain() 一些其他隐藏的信息 MFC对GDI的支持 结语 第3章 MFC中的消息处理 CCmdTarget和消息映射表 窗口消息 MFC消息映射内幕 MFC如何使用消息映射表 进入消息循环:PreTranslateMessage() 结语 第4章 MFC实用类 简单值类型 MFC的集合类 CFile家族:MFC对文件的访问 CExcephon:提供更好的错误处理 结语 第5章 CObject 使用CObject的代价 CObject的特性 宏的介绍 运行时类的信息 MFC中的持续性 CObject对诊断的支持 CObject的诊断支持内幕 组合在一起 投入使用 是否值得 结语 第6章 MFC对话框和控件类 CDialog:模态MFC对话框和非模态MFC对话框 MFC公用对话框 OLE对话框 属性页(也称带标签的对话框) MFC控件类 结语 第7章 MFC的文档/视图结构 为什么要用文档/视图 其他原因 旧的方法 体系结构 文档/视图结构内幕 文档舰图内幕再览 结语 第8章 高级文档舰图结构内幕 CMirrorFile CView打印 CView对打印预览支持的内幕 CView的派生类:CScrollView CView的另一个派生类:CCtrlView 结语 第9章 MFC的增强型用户界面类 CSplitterWnd:MFC分割窗口 MFC的CControlBar体系结构 CMiniFrameWnd MFC的MRU文件链表实现 结语 第10章 MFC的DLL与线程 理解状态 MFC的DLL MFC线程 结语 下一章 第11章 用MFC实现COM MFC和OLE COM 何为COM类 COM接口 GUID 剖析IUnknown接口 COM对象服务器 拥有多个接口的COM类 MFCCOM类 使用MFC创建CoMath MFCCOM和接口映射宏 使用MFC的CoMath类 完成服务器的设计 MFC对类厂的支持 结语 第12章 统一数据传输和MFC 历史回顾 重要的结构 IDataObject接口 OLE剪贴板 MFC的IDataObjeot类 延迟供应 深入了解MFC的IDataObject类 OLE拖放 结语 第13章 使用MFC实现OLE文档 OLE文档101 MFC对OLE文档的支持 使用MFC实现OLE文档服务器 容器朋艮务器的协调工作 使条目无效 保存容器的文档 装载OLE文档 结语 第14章 MFC与自动化 自动化的历史 自动化的功能 使用MFC实现自动化应用程序 自动化的工作机制 COM接口与自动化 实现自动化的另外一种方法:使用类型信息 MFC与自动化 结语:使用“MFC方式”的结果 第15章OLE控件 VBX及其缺陷 OLE控件 写一个OLE控件 在工程里使用OLE控件 它是如何工作的 MFC和OLE控件的容器 OLE控件的生存周期 OLE连接 OLE控件的事件 MFC如何处理事件 技巧:在一个视图中加入一个事件接收器 OLE控件的属性页 结语 附录A MFC源代码导读 MFC编码技术 探索MFC的工具 MFC源代码指南 愉快的旅途 附录B 本书的示例代码 术语表

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值