分享
 
 
 

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

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

1. Microsoft对于<new>的实现版本中的异常处理

上次,我讲述了标准运行库头文件<new>中申明的12个全局函数中的异常行为。这次我将开始讨论Microsoft对这些函数的实现版本。

在Visual C++ 5中,标准运行库头文件<new>提供了这些申明:

namespace std

{

class bad_alloc;

struct nothrow_t;

extern nothrow_t const nothrow;

};

void *operator new(size_t) throw(std::bad_alloc);

void operator delete(void *) throw();

void *operator new(size_t, void *);

void *operator new(size_t, std::nothrow_t const &) throw();

和在第五部分中讲述的标准所要求的相比,Microsoft的<new>头文件版本缺少:

l 所有(三种)形式的operator new[]

l 所有(三种)形式的operator delete[]

l Placement operator delete(void *, void *)

l Placement operator delete(void *, std::nothrow_t const &)

并且,虽然运行库申明了operator new抛出std::bad_alloc,但函数的行为并不符合标准。

如果你使用Visaul C++ 6,<new>头文件有同样的缺陷,只是它申明了operator delete(void *, void *)。

1.1 数组

Visual C++在标准运行库的实行中没有定义operator new[]和operator delete[]形式的版本。幸好,你可以构建自己的版本:

#include <stdio.h>

void *operator new(size_t)

{

printf("operator new\n");

return 0;

}

void operator delete(void *)

{

printf("operator delete\n");

}

void *operator new[](size_t)

{

printf("operator new[]\n");

return 0;

}

void operator delete[](void *)

{

printf("operator delete[]\n");

}

int main()

{

int *p;

p = new int;

delete p;

p = new int[10];

delete[] p;

}

/* When run should yield

operator new

operator delete

operator new[]

operator delete[]

*/

为什么Visual C++的标准运行库缺少这些函数?我不能肯定,猜想是“向后兼容”吧。

operator new[]和operator delete[]加入C++标准比较晚,并且许多年来编译器们还不支持它,所有支持分配用户自定义对象的编译器都定义了operator new和operator delete,并且即使是分配数组对象也将调用它们。

如果一个以前不支持operator new[]和operator delete[]的编译器开始支持它们时,用户自定义的全局operator new和operator delete函数将不再在分配数组对象时被调用。程序仍然能编译和运行,但行为却变了。程序员甚至没法知道变了什么,因为编译器没有报任何错。

1.2 无声的变化

这些无声的变化给写编译器的人(如Microsoft)出了个难题。要知道,C++标准发展了近10年。在此期间,编译器的卖主跟踪标准的变化以确保和最终版本的最大程度兼容。同时,用户依赖于当前可用的语言特性,即使不能确保它们在标准化的过程中得以幸存。

如果标准的一个明显变化造成了符合前标准的程序的行为的悄然变化,编译器的卖主有三种选择:

1. 坚持旧行为,不理符合新标准的代码

2. 改到新行为,不理符合旧标准的代码

3. 让用户指定他们想要的行为

在此处的标准运行库提供operator new[]和operator delete[]的问题上,Micrsoft选择了1。我自己希望他们选择3,对这个问题和其它所有Visual C++不符合标准之处。他们可以通过#pragmas、编译选项或环境变量来判断用户的决定的。

Visual C++长期以来通过形如/Za的编译开关来实行选择3,但这个开关有一个未公开的行为:它关掉了一些标准兼容的特性,然后打开了另外一些。我期望的(想来也是大部分人期望的)是一个完美的调节方法来打开和关闭标准兼容的特性!

(在这个operator new[]和operator delete[]的特例中,我建议你开始使用容器类(如vector)来代替数组,但这是另外一个专栏的事情了。 )

1.3 异常规格申明

Microsoft的<new>头文件正确地申明了非placement的operator new:

void *operator new(std::size_t) throw(std::bad_alloc);

你可以定义自己的operator new版本来覆盖运行库的版本,你可能写成:

void *operator new(std::size_t size) throw(std::bad_alloc)

{

void *p = NULL;

// ... try to allocate '*p' ...

if (p == NULL)

throw std::bad_alloc();

return p;

}

如果你保存上面的函数,并用默认选项编译,Visual C++不会报错。但,如果你将警告级别设为4,然后编译,你将遇到这个信息:

warning C4290: C++ Exception Specification ignored

那么好,如果你自己的异常规格申明不能工作,肯定,运行库的版本也不能。保持警告级别为4,然后编译:

#include <new>

我们已经知道,它申明了一个和我们的程序同样的异常规格的函数。

奇怪啊,奇怪!编译器没有警告,即使在级别4!这是否意味着运行库的申明有些奇特属性而我们的没有?不,它事实上意味着Micorsoft的欺骗行为:

l <new>包含了标准运行库头文件<exception>。

l <exception>包含了非标头文件xstddef。

l xstddef包含了另一个非标头文件yvals.h。

l yvals.h包含了指令#pragma warning(disable:4290)。

l #pragma关闭了特定的级别4的警告,我们在自己的代码中看到的那条。

结论:Visual C++在编译期检查异常规格申明,但在运行期忽略它们。你可以给函数加上异常申明(如throw(std::bad_alloc)),编译器会正确地分析它们,但在运行期这个申明没有效果,就象根本没有写过。

1.4 怎么会这样

在这个专栏的第三部分,我讲述了异常规格申明的形式,却没有解释其行为和效果。Visual C++对异常规格申明的不完全支持给了我一个极好的机会来解释它们。

异常规格申明是函数及其调用者间契约的一部分。它完整列举了函数可能抛出的所有异常。(用标准中的说法,被称为函数 “允许”特定的异常。)

换句话说就是,函数不允许(承诺不抛出)其它任何不在申明中的异常。如果申明有但为空,函数根本不允许任何异常;相反,如果没有异常规格申明,函数允许任何异常。

除非函数与调用者间的契约是强制性的,否则它根本就不值得写出来。于是你可能会想,编译器应该在编译时确保函数没有撒谎:

void f() throw() // 'f' promises to throw no exceptions...

{

throw 1; // ... yet it throws one anyway!

}

惊讶的是,它在Visual C++中编译通过了。

不要认为Visual c++有病,这个例子可以用任何兼容C++的编译器编译通过。我从标准(sub clause 15.4p10)中引下来的:

C++的实现版本不该拒绝一个表达式,仅仅是因为它抛出或可能抛出一个其相关函数所不允许的异常。例如:

extern void f() throw(X, Y);

void g() throw(X)

{

f(); //OK

}

调用f()的语句被正常编译,即使当调用时f()可能抛出g()不允许的异常Y。

是不是有些特别?那么好,如果编译器不强制这个契约,将发生什么?

1.5 运行期系统

如果函数抛出了一个它承诺不抛的异常,运行期系统调用标准运行库函数unexpected()。运行库的缺省unexpected()的实现是调用terminate()来结束函数。你可以调用set_unexpected()函数安装新的unexpected()处理函数而覆盖其缺省行为。

这只是理论。但如前面的Visual C++警告所暗示,它忽略了异常规格申明。因此,Visual C++运行期系统不会调用unexpected()函数,当一个函数违背其承诺时。

要试一下你所喜爱的编译器的行为,编译并运行下面这个小程序:

#include <exception>

#include <stdio.h>

using namespace std;

void my_unexpected_handler()

{

throw bad_exception();

}

void promise_breaker() throws()

{

throw 1;

}

int main()

{

set_unexpected(my_unexpected_handler);

try

{

promise_breaker();

}

catch(bad_exception &)

{

printf("Busted!");

}

catch(...)

{

printf("Escaped!");

}

return 0;

}

如果程序输出是:

Busted!

则,运行期系统完全捕获了违背异常规格申明的行为。反之,如果输出是:

Escaped!

则运行期系统没有捕获违背异常规格申明的行为。

在这个程序里,我安装了my_unexepected_handler()来覆盖运行库的缺省unexpected()处理函数。这个自定义的处理函数抛出一个std::bad_exception类型的异常。此类型有特别的属性:如果unexpected()异常处理函数抛出此类型,此异常能够被(外面)捕获,程序将继续运行而不被终止。在效果上,这个bad_exception对象代替了原始的抛出对象,并向外传播。

这是假定了编译器正确地检测了unexpected异常,在Visual C++中,my_unexpected_handler() 没有并调用,原始的int型异常抛到了外面,违背了承诺。

1.6 模拟异常规格申明

如果你愿意你的设计有些不雅,就可以在Visual C++下模拟异常规格申明。考虑一下这个函数的行为:

void f() throw(char, int, long)

{

// ... whatever

}

假设一下会发生什么?

l 如果f()没有发生异常,它正常返回。

l 如果f()发生了一个允许的异常,异常传到f()外面。

l 如果f()发生了其它(不被允许)的异常,运行期系统调用unexpected()函数。

要在Visual C++下实现这个行为,要将函数改为:

void f() throw(char, int, long)

{

try

{

// ... whatever

}

catch(char)

{

throw;

}

catch(int)

{

throw;

}

catch(long)

{

throw;

}

catch(...)

{

unexpected();

}

}

Visual C++一旦开始正确支持异常规格申明,它的内部代码必然象我在这儿演示的。这意味着异常规格申明将和try/catch块一样导致一些代价,就象我在第四部分中演示的。

因此,你应该明智地使用异常规格申明,就象你使用其它异常部件。任何时候你看到一个异常规格申明,你应该在脑子里将它们转化为try/catch队列以正确地理解其相关的代价。

1.7 预告placement delete的讨论要等到下次。将继续讨论更多的通行策略来异常保护你的设计。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有