分享
 
 
 

C与C++中的异常处理11

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

1. 异常规格申明

现在是探索C++标准运行库和Visual C++在头文件<exception>中申明的异常支持的时候了。根据C++标准(subclause 18.6,“Exception handling” )上的描述,这个头文件申明了:

l 从运行库中抛出的异常对象的基类。

l 任何抛出的违背异常规格申明的对象的可能替代物。

l 在违背异常规格申明的异常被抛出是被调用的函数,以及在其行为上增加东西的钩子(“hook”)。

l 在异常处理过程被终止时被调用的函数,以及在其行为上增加东西的钩子。

我从分析异常规格申明及程序违背它时遭到什么可怕后果开始。分析将针对上面提到的主题,以及通常C++异常处理时的一些杂碎。

1.1 异常规格申明回顾

异常规格申明是C++函数申明的一部分,它们指定了函数可以抛出什么异常。例如,函数

void f1() throw(int)

可以抛出一个整型异常,而

void f2() throw(char *, E)

可以抛出一个char *或一个E(这里E是用户自定义类型)类型的异常。一个空的规格申明

void f3() throw()

表明函数不抛出异常,而没有规格申明

void f4()

表明函数可以抛出任何东西。注意语法

void f4() throw(...)

比前面的“抛任何东西”的函数更好,因为它类似“捕获任何东西”

catch(...)

然而,认可“抛任何东西” 的函数就允许了那些在异常规格申明存在前写下的函数。

1.2 违背异常规格申明

迄今为止,我写的都是:函数可能抛出在它的异常规格申明中描述的异常。“可能”有些单薄,“必须”则有力些。“可能”表示了函数可以忽略它们的异常规格。你也许认为编译器将禁止这种行为:

void f() throw() // Promises not to throw...

{

throw 1; // ...but does anyway - error?

}

但你错了。用Visual C++试一下,你将发现编译器保持沉默,它没有发现编译期错误。实际上,在我所用过的编译器中,没有一个报了编译期错误。

话虽这么说,但异常规格申明有它的规则的,函数违背它将遭受严重后果的。不幸的是,这些后果表现在运行期错误而不是编译期。想看的话,把上面的小段代码放到一个完整程序中:

void f() throw()

{

throw 1;

}

int main()

{

f();

return 0;

}

当程序运行时将发生什么?f()抛出一个int型异常,违背了它的契约。你可能认为这个异常将从main()中漏入运行期库。基于这个假设,你倾向于使用一个简单的try块:

#include <stdio.h>

void f() throw()

{

throw 1;

}

int main()

{

try

{

f();

}

catch (int)

{

printf("caught int\n");

}

return 0;

}

来捕获这个异常,以防止它漏出去。

实际上,如果你用Visual C++ 6编译并运行,你将得到:

caught int

你再次奇怪throw()异常规格实际做了什么有用的事,除了增加了源代码的大小和看起来比较快感。你的奇怪感觉将变得迟钝,只要一回想到前面说了多少Visual C++违背C++标准的地方,只不过再多一个新问题:Visaul C++正确地处理了违背异常规格申明的情况了吗?

1.3 调查说明……

没有!

这个程序的行为符合标准吗?catch语句不该进入的。来自于标准(subclauses 15.5.2 and 18.6.2.2):

l 一个异常规格申明保证只有被列出的异常被抛出。

l 如果带异常规格申明的函数抛出了一个没有列出的异常,函数

l void unexpected()在退完栈后立即被调用。

l 函数unexpected()将不会返回……

当一个函数试图抛出没有列出的异常时,通过unexpected()函数调用了一个异常处理函数。这个异常处理函数的默认实现是调用terminate() 来结束程序。

在我给你一个简短的例程后,我将展示Visual C++的行为怎么样地和标准不同。

1.4 unexpected()函数指南

unexpected()函数是标准运行库在头文件<exception>中申明的函数。和其它大部分运行库函数一样,unexpected()函数存在于命名空间std中。它不接受参数,也不返回任何东西,实际上unexpected()函数从不返回,就象abort()和exit()一样。如果一个函数违背了它自己的异常规格申明,unexpected()函数在退完栈后被立即调用。

基于我对标准的理解,运行库的unexpected()函数的实现理论上是这样的:

void _default_unexpected_handler_()

{

std::terminate();

}

std::unexpected_handler _unexpected_handler =

_default_unexpected_handler;

void unexpected()

{

_unexpected_handler();

}

(_default_unexpected_handler和_unexpected_handler是我虚构的名字。你的运行库的实现可能使用其它名称,完全取决于其实现。)

std::unexpected()调用一个函数来真正处理unexpected的异常。它通过一个隐藏的指针(_unexpected_handler,类型是std::unexpected_handler)来引用这个处理函数的。运行库提供了一个默认处理函数(default_unexpected_handler()),它调用std::terminate()来结束程序。

因为是通过指针_unexpected_handler间接调用的,你可以将内置的调用_default_unexpected_handler改为调用你自己的处理函数,只要这个处理函数的类型兼容于std::unexpected_handler:

typedef void (*unexpected_handler)();

同样,处理函数必须不返回到它的调用者(std::unexpected())中。没人阻止你写一个会返回的处理函数,但这样的处理函数不是标准兼容的,其结果是程序的行为有些病态。

你可以通过标准运行库的函数std::set_unexpected()来挂接自己的处理函数。注意,运行库只维护一个处理函数来处理所有的unexpected异常;一旦你调用了set_unexpected()函数,运行库将不再记得前一次的处理函数。(和atexit()比较一下,atexit()至少可以挂32重exit处理函数。)要克服这个限制,你要么在不同的时间设置不同的处理函数,要么使你的处理函数在不同的上下文时有不同的行为。

1.5 Visual C++ vs unexpected

试一下这个简单的例子:

#include <exception>

#include <stdio.h>

#include <stdlib.h>

using namespace std;

void my_unexpected_handler()

{

printf("in unexpected handler\n");

abort();

}

void throw_unexpected_exception() throw(int)

{

throw 1L; // violates specification

}

int main()

{

set_unexpected(my_unexpected_handler);

throw_unexpected_exception();

printf("this line should never appear\n");

return 0;

}

用一个标准兼容的编译器编译并运行,程序结果是:

in unexpected handler

可能接下来是个异常异常终止的特殊(因为有abort()的调用)。但用Visual C++编译并运行,程序会抛出“Unhandled exception”对话框。关闭对话框后,程序输出:

this line should never appear

必须承认,Visual C++没有正确实现unexpected()。这个函数被申明在<exception>中,运行期库中有其实现,只不过这个实现不做任何事。

实际上,Visual C++甚至没有正确地申明,用这个理论上等价的程序可以证明:

#include <exception>

#include <stdio.h>

#include <stdlib.h>

//using namespace std;

void my_unexpected_handler()

{

printf("in unexpected handler\n");

abort();

}

void throw_unexpected_exception() throw(int)

{

throw 1L; // violates specification

}

int main()

{

std::set_unexpected(my_unexpected_handler);

throw_unexpected_exception();

printf("this line should never appear\n");

return 0;

}

Visual C++不能编译这个程序。查看<exception>表明:set_unexpected_handler()被申明为全局函数而不是在命名空间std中。实际上,所有的unexpected族函数都被申明为全局函数。

底线:Visual c++能编译使用unexpected()等函数的程序,但运行时的行为是不正确的。

我希望Microsoft能在下一版中改正这些问题。在未改正前,当讨论涉及到unexpected()时,我建议你使用标准兼容的C++编译器。

1.6 维持程序存活

在我所展示的简单例子中,程序在my_unexpected_handler()里停止了。有时,让程序停止是合理和正确的;但更多情况下,程序停止是太刺激了,尤其是当unexpected异常表明的是程序只轻微错误。

假定你想处理unexpected异常,并恢复程序,就象对大多数其它“正常”异常一样。因为unexpected()从不返回,程序恢复似乎不可能,除非你看了标准的subclause 15.5.2:

unexpected()不该返回,但它可以throw(或re-throw)一个异常。如果它抛出一个新异常,而这异常是异常规格申明允许的,搜索另外一个异常处理函数的行为在调用unexpected()的地方继续进行。

太好了!如果my_unexpected_handler()抛出一个允许的异常,程序就能从最初的违背异常规格申明的地方恢复了。在我们的例子里,最初的异常规格申明允许int型的异常。根据上面的说法,如果my_unexpected_handler抛出一个int异常,程序将能继续了。

基于这种猜测,试一下:

#include <exception>

#include <stdio.h>

void my_unexpected_handler()

{

printf("in unexpected handler\n");

throw 2; // allowed by original specification

//abort();

}

用标准兼容的编译器编译运行,程序输出:

in unexpected handler

program resumed

和期望相符。

抛出的int异常和其它异常一样顺调用链传递,并被第一个相匹配的异常处理函数捕获。在我们的例子里,程序的控制权从my_unexpected_handler()向std::unexpected()再向main()回退,并在main()中捕获异常。用这种方法,my_unexpected_handler()变成了一个异常转换器,将一个最初的“坏”的long型异常转换为一个“好”的int型异常。

结论:通过转换一个unexpected异常为expected异常,你能恢复程序的运行。

1.7 预告

下次,我将结束std::unexpected()的讨论:揭示在my_unexpected_handler()中抛异常的限制,探索运行库对这些限制的补救,并给出处理unexpected异常的通行指导原则。我也将开始讨论运行库函数std::terminate()的相关内容。

void throw_unexpected_exception() throw(int)

{

throw 1L; // violates specification

}

int main()

{

std::set_unexpected(my_unexpected_handler);

try

{

throw_unexpected_exception();

printf("this line should never appear\n");

}

catch (int)

{

printf("program resumed\n");

}

return 0;

}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有