窥探try ... catch与__try ... __except的区别

简介: VC中的这两个东西肯定谁都用过, 不过它们之间有什么区别, 正好有时间研究了一下, 如果有错误欢迎拍砖.基于VC2005, 32位XP 平台测试通过. 估计对于其他版本的VC和操作系统是不通用的.1. try .

VC中的这两个东西肯定谁都用过, 不过它们之间有什么区别, 正好有时间研究了一下, 如果有错误欢迎拍砖.
基于VC2005, 32位XP 平台测试通过. 估计对于其他版本的VC和操作系统是不通用的.

1. try ... catch

这个是C++语言定义的, 每个C++都有对其的不同的实现. 使用也很简单. 比如我们有一个函数, 读入年龄. 如果<=0 或者 >=100, 抛出异常:

int readAge() {
   int age = 读入年龄;
   if (age <=0 || age >= 100) {
      throw AgeException(age);
   }
   return age;
}

其中 AgeException 的定义为

class AgeException {
   public:
   int errorAge;
   AgeException(int age) {
      errorAge = age;
   }
};

在使用的时候也比较简单,

try {
   int i = readAge();
   printf("Age inputed is %d", i);
} catch (AgeException e) {
   printf("error. Age inputed = %d and is not valid.", e.errorAge);
}

2. __try ... __except

这个是VC自己定义的不是C++的关键字. VC在编译__try ... __except的时候, 会按照Windows SEH(结构化异常)处理的规则, 把异常处理部分加入到当前线程的异常处理链中. 这部分不详细写了, SEH处理在网上的文章一搜一大把.

3. try...catch 与 __try ... __except 使用上的区别

对于上面的AgeException, 我们也可以使用__try... __except 来处理:

__try {
   int i = readAge();
   printf("Age inputed is %d", i);
} __except (EXCEPTION_EXECUTE_HANDLER) {
   printf("Age is not correct.");
}

但是, 对于__try ... __except 能够处理的异常(比如下面的代码), C++异常处理try .. catch 不能够捕获(Catch段不能执行):

try {
   int *p = NULL;
   *p = 0;
} catch (...) {
   printf("Exception occured.");
}

注: 这里其实和编译器有关, VC2005由/EH加上参数来控制, 详情参见http://msdn.microsoft.com/en-us/library/1deeycx5(VS.80).aspx . 这里讨论的是默认的情况, 不处理的时候.

这是为什么呢. 仔细看了下, 当我们在程序里面throw出来一个异常的时候, 调试器(比如VC, WinDBG)会记录下面一个事件:

First-chance exception at 0x7c812afb (kernel32.dll) in trycatch.exe: Microsoft C++ exception: AgeException at memory location 0x0012fc98..

也就是说, 在VC中, throw出来的都是Microsoft C++ exception. 只有这种Exception才能被try...catch捕获. 同样, 用WinDBG装载上面的程序

__try {
   int i = readAge();
   printf("Age inputed is %d", i);
} __except (EXCEPTION_EXECUTE_HANDLER) {
   printf("Age is not correct.");
}

会发现, 出现的异常为C++ exception, 异常代码为0xe06d7363:

也就是说, 在C++中throw出来的异常是一种特殊的类型的异常, 是微软专门为VC++实现的,异常代码为0xe06d7363. (有意思的是ASCII码为0x6d, 0x73, 0x63的字符为msc)

到这里我们基本可以得出一个结论, try...catch和__try...__except其实从本质上来说是一回事, 他们从根源上来说都是用到了Windows的SEH处理机制. 不同点在于:

-) try...catch 只处理异常代码为0xe06d7363的C++ exception, 不会理会其他的;
-) try...catch 对于编译器来说做了一些额外的工作, 但是最终的实现是和__try...__except都要归结于SEH
-) try...catch 多了一些额外的传递具体的异常信息的部分(catch的是何种异常. 不像是__try...__except, 需要用ExceptionCode去判断)

想到这, 想到了下面一个问题, 就是VC++编译器是如何知道catch的异常信息的呢? 换句话说, 对于下面的代码, 我们知道出现了异常, 但是怎么得到异常的信息的呢?

__try {
   int i = readAge();
   printf("Age inputed is %d", i);
} __except (EXCEPTION_EXECUTE_HANDLER) {
   printf("Age is not correct."); //如何知道readAge中throw出来的AgeException?
}

为了调试方便, 把异常类和抛出异常的代码修改一下, 在创建异常的时候传递错误的age和一条消息

class AgeException {
public:
   int errorAge;
   char *p;
   AgeException(int age, char* msg) {
      errorAge = age;
      p = msg;
   }
};

int readAge() {
   int age = 123; //
   if (age <=0 || age >= 100) {
      throw AgeException(age, "Age is outof range.");
   }
   return age;
}

用WinDBG装入, 运行, 出现了C++异常后, 使用.exr -1 命令查看最近出现的异常:


可以看出, 这个异常为前面讨论的C++异常(0xe06d7363类型), 带有3个参数, 每个参数, 参数分别为0x19930520, 0x0012fca4, 0x00417bc8. 因为我没有找到C++异常中参数的含义, 只能猜了(哪位如果知道请赐教).

考虑到抛出异常的代码抛出的异常类型为AgeException, 那么很自然想到抛出的异常作为一个指针存储在参数中. 因为没有资料, 只能挨个试验了. 使用命令dt trycatch!AgeException 地址, 来把trycatch模块(编译出的程序名是trycatch.exe)中地址的内容按照类AgeException显示出来:



果然, 第一个参数0x19930520里面是不是我们想要的; 当输入第二个参数的时候, 该地址中的内容和预料的一致, 是我们抛出的异常中的内容. 这样验证了猜想. try...catch的工作流程为:

-) 编译器在编译try...catch的时候, 也是利用Windows的SEH, 只不过仅仅针对C++异常(0xe06d7363类型)进行处理;
-) 抛出异常的时候(throw), 把生成的异常类的实例地址, 保存在异常信息的第二个参数中
-) catch异常的时候, 从异常信息第2个参数中读出地址, 并转化为异常类的实例, 供程序使用.

要使用__try...__except模拟上述的过程, 程序可以改为:

__try {
   int i = readAge();
   printf("Age inputed is %d", i);
} __except (extract(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER) {
   printf("Exception happene.");
}

void extract(LPEXCEPTION_POINTERS p) {
   int d = p->ExceptionRecord->NumberParameters; //参数数目, 这里没用到
   unsigned int * ex = (unsigned int *) p->ExceptionRecord->ExceptionInformation[1]; //第二个参数
   AgeException * e = (AgeException *) ex; //转换,得到异常类的实例
   printf("==> %d \n", e->errorAge); //异常的信息可以知道了.
    printf("==> %s \n", e->p); //异常的信息可以知道了.
}

运行, 和预期的结果是一致的.

最后可以得到结论(不知道这样说是否完全正确) :

try...catch是编译器对__try ... __except的一个包装; 该包装仅处理C++异常类型, 但是提供了比较方便的方法来传递抛出的异常信息, 这样程序员能够比较方便的处理异常, 而不用想上面的例子那样要手工去异常信息中去取.

相关文章
|
4月前
|
Java UED 开发者
Java异常处理新玩法:throw关键字,你的错误管理利器!
Java异常处理新玩法:throw关键字,你的错误管理利器!
35 1
|
2天前
|
Java 程序员 数据库连接
90%开发者都分不清!final、finally、finalize的终极区别揭秘
小米,29岁程序员,分享Java面试中常见的final、finally、finalize区别。final确保不可变性,finally保证代码总会执行,finalize在对象被垃圾回收前做清理工作。通过故事讲解,帮助理解三者在Java资源管理中的不同角色。
11 3
|
24天前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
45 2
|
2月前
|
Oracle Java 关系型数据库
农行1面:说说 final,finally,finalize的区别
在 Java中,“final”、“finally”和“finalize”是三个不同的关键字或方法,尽管它们的名字相似,但在功能和用途上却有显著的区别,这篇文章我们继续分析一篇农行1面的题目:说说 final,finally,finalize的区别。
19 3
|
4月前
|
安全 Java 开发者
【技术咖必看】Java异常处理新境界:throws关键字,打造万无一失的方法签名!
【技术咖必看】Java异常处理新境界:throws关键字,打造万无一失的方法签名!
40 3
|
4月前
|
网络协议 Java 数据库连接
13 Java异常(异常过程解析、throw、throws、try-catch关键字)
13 Java异常(异常过程解析、throw、throws、try-catch关键字)
95 2
|
6月前
|
数据库 UED 开发者
try-catch的作用及使用场景
`try-catch`是错误处理的关键结构,用于执行可能出错的代码并捕获异常,防止程序崩溃。它用于异常处理、资源管理、错误恢复、日志记录和控制业务逻辑。例如,在数据库操作、文件操作、网络请求及用户输入处理中常见其身影,确保程序稳定性和用户体验。
373 0
|
6月前
|
Java UED 开发者
【技术解密】Java异常处理新玩法:throw关键字,你的错误管理利器!
【6月更文挑战第19天】Java异常处理关键在于`throw`,它用于主动抛出异常,确保程序健壮性。例如,当年龄验证失败时,`IllegalArgumentException`被`throw`,提供错误详情。自定义异常如`CustomException`能增强错误信息。此外,通过构建异常链,如在`DataProcessingException`中嵌套`IOException`,保持原始堆栈信息,提供更全面的错误上下文。掌握`throw`能提升错误管理,打造稳定软件。
53 5
|
7月前
|
Java
Java异常处理:解释一下异常的传播机制。
Java异常处理:解释一下异常的传播机制。
115 1
Catch块异常多类抓取
Catch块异常多类抓取
59 0