正常和异常异常情况和处理
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
正常和异常
程序执行中可能遇到非正常情况,正常控制流无法继续,需要特殊处理 出现非正常情况的原因可能是由于环境因素(不符合要求的外部输入或用 户交互),也可能是程序不同部分间的相互作用 常见实例:存储分配申请无法得到满足,读入数据时找不到媒介或读入出 错,调用读栈操作时遇到栈空,压栈操作遇到栈满等等 遇到这类情况时应该如何处理呢?看一个实际例子(数据结构): int stack::top() { if (empty()) { // 怎么办? } ... ... } 常见处理方式1(教科书中常见): int stack::top() { if (empty()) exit(1); ... ... } 错误!不能用于实际的程序设计 底层服务绝不能自主决定终止程序,它不掌 握做这种决策的信息,也没有这种权力
C 语言标准库里为处理非正常情况提供了两套机制: • setjmp/longjmp 机制。主要功能:保存执行现场,以支持在随后的执行中 (主要是从嵌套调用的子程序里)直接跳回前面保存的现场,这时可以换 一条路径重新执行(例如处理底层发现的运行异常) • signal 机制。主要功能:可针对一些“信号”定义特殊的处理函数(信号处 理器),在信号被引发时自动执行对应处理器。C 标准库定义了若干标准 的信号(会自动引发),可定制针对它们的处理器 一些环境提供了类似机制。这类机制的共同缺陷是太低级,而且不安全
2012年4月
程序设计语言原理 ——第6章
60
异常情况和处理
问题:
发生异常是低级事件,发现异常的通常是底层模块。如硬件、基础运行系 统、库模块、底层服务模块。异常的正确处理只能根据应用的需要来确定, 只有适当的上层模块才可能知道正确合理的处理方式 检查发现异常情况的代码与能处理它的代码之间可能跨越多层调用(按常 规的控制流,控制转移的最大步骤是子程序调用和返回)
2012年4月
程序设计语言原理 ——第6章
61
异常情况和处理
由于异常(包括错误和其他一些特殊情况)的处理很重要,编程中需要大量 写这方面的代码,因此应该在语言层面上为之提供支持 异常处理的目标是尽可能挽救已经出问题的执行过程,使之有可能继续进行 下去(换一种方式?抛弃某些枝节?弱化某些功能?等等) 对异常情况的处理,最常见的处理模式是:在正常处理中发现异常情况—— 控制转至特殊处理阶段——处理完成——返回正常控制流 对语言层解决方案的一些要求: • 能比较自然地反映从异常检查代码到处理代码,以及从异常处理代码回 到正常执行过程的控制转移。将这些转移屏蔽在语言之下自动进行 • 有清晰的语义模型,程序员容易理解,容易正确使用 • 正常控制流与异常处理控制流相互隔离 • 有机地融合到现有的基本语言机制中(以上都是语法/语义方面的考虑) • 效率较高,不出现异常情况时最好是不影响程序的执行(效率)
2012年4月
程序设计语言原理 ——第6章
62
异常处理
主流语言里最早的专用错误处理机制是PL/1语言的ON语句。程序出现异常 时自动转到相应 ON 语句,处理完毕后通过 goto 转出(控制流很难把握) 1975 年 J. Goodenough 在 CACM 发表论文提出了结构化异常处理的概念, 建议的基本处理框架:把异常处理结构附在正常结构上,运行中出现异常时 控制自动转过去处理异常,处理完成后回到正常控制流 异常处理机制的研究和发展基本上都是沿着这个方向进行的 异常处理的第一个想法是区分两种控制流: 正常控制结构描述正常控制流;异常控制结构描述出现异常时的处理过程 定义良好的在两种控制结构之间的自动转移规则 • 在正常控制结构中出现异常情况,控制自动转入异常控制结构 • 异常处理完成,运行自动转回正常控制结构(怎么转?转到哪?) 结构化异常处理:将异常控制结构附着在某些正常控制结构上,由它们来 捕捉所附着的正常结构的执行中发生的异常
程序设计语言原理 ——第6章
2012年4月
57
异常情况和处理
处理方式2(教科书中常见): 错误!完全不能用于实际程序设计 int stack::top() { 产生输出信息后继续运行,程序已进 if (empty()) 入非正常状态,后果无法预料 cout << "Stack empty"; 子程序向程序使用者报告,而人通常 ... ... 无法直接干预程序的内部执行 } 处理方式3(教科书中可见): 通常用于帮助查找错误,在调试执行中出 错时assert 产生错误报告并终止程序 int stack::top() { assert(!empty()); 通常在生成执行代码时关闭断言(定义宏 ... ... NDEBUD,否则类似于用 exit) } 如果运行中出现访问空栈的情况: • 不关闭断言,效果同上面处理方式1 • 关闭断言,效果同上面处理方式2
2012年4月
程序设计语言原理 ——第6章
58
异常情况和处理
处理方式 4(教科书中可见): 方法正确 int stack::top() { • 下层检查错误情况并报告 if (empty()) return ECODE1; • 上层检查完成状态并处理 ... ... } 但: 使用这种函数: • 能否找到合适的错误码? if((n=s.top()) == ECODE1) • 程序中很难做充分的检查 ... // 出错处理 else ... // 正常处理 缺点:如果每个函数都设置出错返回值, 在每个调用位置都检查,将严重干扰程序 的正常控制流 处理错误的代码和正常处理过程混在一起, 程序会变得无法控制。处理逻辑变得很不 清晰,使程序难以开发,难以阅读
2012年4月
程序பைடு நூலகம்计语言原理 ——第6章
59
异常情况和处理
另一常见技术:设置专用于记录执行状态的全局变量,执行中遇异常情况时 给变量赋特殊值。程序其他部分可以检查这个变量,并根据情况处理 例:C标准库定义了errno,初值为 0 表示无异常。一些库函数(特别是数 学函数)出现异常情况时把errno设为非0。检查它就能确定执行情况 问题与返回出错码的方式类似: • 每时每刻检查,严重干扰正常处理部分的开发、理解和维护 • 不及时检查就可能忽略实际出现的错误 可见:常规的描述手段对错误处理描述的支持不足 1980年代末一项研究显示,程序里处理错误的代码可达总代码量的2/3 大量处理异常情况的代码与正常控制流代码混在一起,极大地影响着程序的 开发,破坏程序的可读性、可理解性和可维护性
程序执行中可能遇到非正常情况,正常控制流无法继续,需要特殊处理 出现非正常情况的原因可能是由于环境因素(不符合要求的外部输入或用 户交互),也可能是程序不同部分间的相互作用 常见实例:存储分配申请无法得到满足,读入数据时找不到媒介或读入出 错,调用读栈操作时遇到栈空,压栈操作遇到栈满等等 遇到这类情况时应该如何处理呢?看一个实际例子(数据结构): int stack::top() { if (empty()) { // 怎么办? } ... ... } 常见处理方式1(教科书中常见): int stack::top() { if (empty()) exit(1); ... ... } 错误!不能用于实际的程序设计 底层服务绝不能自主决定终止程序,它不掌 握做这种决策的信息,也没有这种权力
C 语言标准库里为处理非正常情况提供了两套机制: • setjmp/longjmp 机制。主要功能:保存执行现场,以支持在随后的执行中 (主要是从嵌套调用的子程序里)直接跳回前面保存的现场,这时可以换 一条路径重新执行(例如处理底层发现的运行异常) • signal 机制。主要功能:可针对一些“信号”定义特殊的处理函数(信号处 理器),在信号被引发时自动执行对应处理器。C 标准库定义了若干标准 的信号(会自动引发),可定制针对它们的处理器 一些环境提供了类似机制。这类机制的共同缺陷是太低级,而且不安全
2012年4月
程序设计语言原理 ——第6章
60
异常情况和处理
问题:
发生异常是低级事件,发现异常的通常是底层模块。如硬件、基础运行系 统、库模块、底层服务模块。异常的正确处理只能根据应用的需要来确定, 只有适当的上层模块才可能知道正确合理的处理方式 检查发现异常情况的代码与能处理它的代码之间可能跨越多层调用(按常 规的控制流,控制转移的最大步骤是子程序调用和返回)
2012年4月
程序设计语言原理 ——第6章
61
异常情况和处理
由于异常(包括错误和其他一些特殊情况)的处理很重要,编程中需要大量 写这方面的代码,因此应该在语言层面上为之提供支持 异常处理的目标是尽可能挽救已经出问题的执行过程,使之有可能继续进行 下去(换一种方式?抛弃某些枝节?弱化某些功能?等等) 对异常情况的处理,最常见的处理模式是:在正常处理中发现异常情况—— 控制转至特殊处理阶段——处理完成——返回正常控制流 对语言层解决方案的一些要求: • 能比较自然地反映从异常检查代码到处理代码,以及从异常处理代码回 到正常执行过程的控制转移。将这些转移屏蔽在语言之下自动进行 • 有清晰的语义模型,程序员容易理解,容易正确使用 • 正常控制流与异常处理控制流相互隔离 • 有机地融合到现有的基本语言机制中(以上都是语法/语义方面的考虑) • 效率较高,不出现异常情况时最好是不影响程序的执行(效率)
2012年4月
程序设计语言原理 ——第6章
62
异常处理
主流语言里最早的专用错误处理机制是PL/1语言的ON语句。程序出现异常 时自动转到相应 ON 语句,处理完毕后通过 goto 转出(控制流很难把握) 1975 年 J. Goodenough 在 CACM 发表论文提出了结构化异常处理的概念, 建议的基本处理框架:把异常处理结构附在正常结构上,运行中出现异常时 控制自动转过去处理异常,处理完成后回到正常控制流 异常处理机制的研究和发展基本上都是沿着这个方向进行的 异常处理的第一个想法是区分两种控制流: 正常控制结构描述正常控制流;异常控制结构描述出现异常时的处理过程 定义良好的在两种控制结构之间的自动转移规则 • 在正常控制结构中出现异常情况,控制自动转入异常控制结构 • 异常处理完成,运行自动转回正常控制结构(怎么转?转到哪?) 结构化异常处理:将异常控制结构附着在某些正常控制结构上,由它们来 捕捉所附着的正常结构的执行中发生的异常
程序设计语言原理 ——第6章
2012年4月
57
异常情况和处理
处理方式2(教科书中常见): 错误!完全不能用于实际程序设计 int stack::top() { 产生输出信息后继续运行,程序已进 if (empty()) 入非正常状态,后果无法预料 cout << "Stack empty"; 子程序向程序使用者报告,而人通常 ... ... 无法直接干预程序的内部执行 } 处理方式3(教科书中可见): 通常用于帮助查找错误,在调试执行中出 错时assert 产生错误报告并终止程序 int stack::top() { assert(!empty()); 通常在生成执行代码时关闭断言(定义宏 ... ... NDEBUD,否则类似于用 exit) } 如果运行中出现访问空栈的情况: • 不关闭断言,效果同上面处理方式1 • 关闭断言,效果同上面处理方式2
2012年4月
程序设计语言原理 ——第6章
58
异常情况和处理
处理方式 4(教科书中可见): 方法正确 int stack::top() { • 下层检查错误情况并报告 if (empty()) return ECODE1; • 上层检查完成状态并处理 ... ... } 但: 使用这种函数: • 能否找到合适的错误码? if((n=s.top()) == ECODE1) • 程序中很难做充分的检查 ... // 出错处理 else ... // 正常处理 缺点:如果每个函数都设置出错返回值, 在每个调用位置都检查,将严重干扰程序 的正常控制流 处理错误的代码和正常处理过程混在一起, 程序会变得无法控制。处理逻辑变得很不 清晰,使程序难以开发,难以阅读
2012年4月
程序பைடு நூலகம்计语言原理 ——第6章
59
异常情况和处理
另一常见技术:设置专用于记录执行状态的全局变量,执行中遇异常情况时 给变量赋特殊值。程序其他部分可以检查这个变量,并根据情况处理 例:C标准库定义了errno,初值为 0 表示无异常。一些库函数(特别是数 学函数)出现异常情况时把errno设为非0。检查它就能确定执行情况 问题与返回出错码的方式类似: • 每时每刻检查,严重干扰正常处理部分的开发、理解和维护 • 不及时检查就可能忽略实际出现的错误 可见:常规的描述手段对错误处理描述的支持不足 1980年代末一项研究显示,程序里处理错误的代码可达总代码量的2/3 大量处理异常情况的代码与正常控制流代码混在一起,极大地影响着程序的 开发,破坏程序的可读性、可理解性和可维护性