标题:用C语言处理程序的异常简介
只看楼主
lingluoz
Rank: 2
来 自:苏州科技学院
等 级:新手上路
威 望:4
帖 子:749
专家分:0
注 册:2008-2-2
结帖率:100%
 问题点数:0 回复次数:9 
用C语言处理程序的异常简介
平时我们用c语言来写程序,经常会遇见一种情况就是使用了一个NULL的指针,然后结果显而易见是程序挂掉了,这个就是所谓的异常。这个显然是一个废话,但是本文研究的主题就是要怎样才能捕获这种挂掉的异常,然后让程序正确运行。这个就是需要一点技术了,当然对于C++这个异常的捕获try语句是必修课,但是对于C语言来说有点问题了,虽然有__try这个类似的异常处理,但是毕竟不是C标准里面的东西,这里介绍一种标准C语言通过调用API简单有效的捕获程序异常的方法

首先有这样的一段程序
程序1
#include <stdio.h>

int i=0;
int *p=NULL;

int main()
{
    *p=0;
    return 0;
}

编译执行,谁都知道结果是程序挂掉。但是不知道大家有没有研究过这个挂掉的过程,有兴趣的话自己去研究一下吧,最重要的一点记住就行。Windows 程序设计中最重要的理念就是消息传递,事件驱动。我们可以把异常也当作是一种消息,应用程序发生异常时就触发了该消息并告知系统。系统接收后同样会找它的“回调函数”,也就是我们的异常处理例程。
如果你这些看不懂,不要紧,下面来看看实例吧:)
首先要定义一个异常处理函数,它的函数原型是:
long WINAPI ExceptionFilter(EXCEPTION_POINTERS * lParam)
这个不是你程序当中要调用的,是windows系统调用的,所以一定要确保函数是这种原型。至于EXCEPTION_POINTERS这个结构储存关于异常的类型地点等一些信息,这个很复杂,估计把它介绍完我今天晚上不要睡觉了(现在是晚上0:34)。这个函数有一个返回值long类型的,但是有效的返回值只有三个,并且这个返回值是送给windows的,让windows根据它的值来决定这个程序是继续运行还是关闭。
三个有效的值分别是
EXCEPTION_EXECUTE_HANDLER=1 已处理这个异常,程序正常结束
EXCEPTION_CONTINUE_SEARCH  = 0   不处理这个异常转交windows处理,就是弹出一个程序错误的框框
EXCEPTION_CONTINUE_EXECUTION = -1  已修复这个错误,返回错误出现的地方继续执行,8过这个是相当困难的:P

程序2 捕获一个异常然后让程序正常结束
#include <stdio.h>
#include <windows.h>

int i=0;
int *p=NULL;

long WINAPI ExceptionFilter(EXCEPTION_POINTERS * lParam)
{
    puts("DADA...");
    return 1;
}

int main()
{
    SetUnhandledExceptionFilter(ExceptionFilter);
    *p=0;
    return 0;
}

SetUnhandledExceptionFilter(ExceptionFilter);这条语句设定了一个异常处理函数,就是告诉windows如果如果本程序出了一个错误就去执行ExceptionFilter函数。
*p=0;爆出了一个无效内存访问的错误,接着windows就会去调用以前注册过的ExceptionFilter函数。这个函数的功能就是输出DADA…这个字符串,然后返回1给windows来结束这个程序。
整个过程就是这样的,注意运行的时候选择Start Without Debug否则只会让调试器爆出一个错误的提示。

接下去研究怎么捕获异常来修正这个错误,让程序继续运行。就是遇到异常就先给p赋值&i再返回出错的地方继续执行。这里就要用到一个setjmp的东西了,这个是c标准里面的,我简单介绍一下
longjmp
语法:
#include <setjmp.h>  void longjmp( jmp_buf envbuf, int status );
功能: 函数使程序从前次对setjmp()的调用处继续执行。参数envbuf一般通过调用setjmp()设定。参数status 为setjmp()的返回值,用来指示不同地点longjmp()的执行. status 不能设定为零。
setjmp
语法:
#include <setjmp.h>  int setjmp( jmp_buf envbuf );
功能: 函数将系统栈保存于envbuf中,以供以后调用longjmp()。当第一次调用setjmp(),它的返回值为零。之后调用longjmp(),longjmp()的第二个参数即为setjmp()的返回值。

程序3:调用longjmp来纠正错误,让程序继续运行

#include <stdio.h>
#include <windows.h>
#include <setjmp.h>

int i=0;
int *p=NULL;
int status;
jmp_buf envbuf;

long WINAPI ExceptionFilter(EXCEPTION_POINTERS * lParam)
{
    p=&i;
    //修改p至正确值
    longjmp(envbuf,status);
    //跳回到setjmp设定的地方
    return 1;
    //这句是摆设没有意义
}

int main()
{
    SetUnhandledExceptionFilter(ExceptionFilter);
    //设定异常处理函数
    status=setjmp(envbuf);  
    //设定longjmp跳跃目的地
    *p=0;
    puts("DADA...");
    //屏幕上出现DADA...表示程序正确运行了:)
    return 0;
}

就是这么简单.西西:)

写在后面的话
有一个问题,用EXCEPTION_CONTINUE_EXECUTION来代替longjmp返回出错的地方不是更简单吗,答案是不可以。究竟是为什么,有兴趣的人可以自己去把程序反汇编研究一下。出错的地方指的是发生异常的指令所在的位置而不是这条语句的位置。
我的邮箱lingluoz@

P.S.
睡了。。。好困。。
搜索更多相关主题的帖子: 简介 C语言 
2008-11-24 01:23
lingluoz
Rank: 2
来 自:苏州科技学院
等 级:新手上路
威 望:4
帖 子:749
专家分:0
注 册:2008-2-2
得分:0 
8知道大家看得懂吗

Murphy's Law :
If there are two or more ways to do something, and one of those ways can result in a catastrophe, then someone will do it.
2008-11-24 01:25
风居住的街道
Rank: 1
等 级:新手上路
帖 子:374
专家分:0
注 册:2008-10-24
得分:0 
其实……C语言的__try块根本就是用setjump/longjump模拟的……
2008-11-24 06:57
风居住的街道
Rank: 1
等 级:新手上路
帖 子:374
专家分:0
注 册:2008-10-24
得分:0 
至于原因,是不是说写NULL这个操作是通过寄存器完成的,而在FILTER里面无法改变主程序的寄存器使用?
2008-11-24 07:02
风居住的街道
Rank: 1
等 级:新手上路
帖 子:374
专家分:0
注 册:2008-10-24
得分:0 
最后罗嗦一句,通常情况下C++的异常就是用中断——也就是WINDOWS的错误处理实现的。它们根本就是一类东西。
2008-11-24 07:08
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
得分:0 
能不能不通过API,以便DOS下也能用呢?

从BFS(Breadth First Study)到DFS(Depth First Study)
2008-11-24 07:36
forever74
Rank: 12Rank: 12Rank: 12
来 自:CC
等 级:贵宾
威 望:49
帖 子:1636
专家分:3940
注 册:2007-12-27
得分:0 
DOS下滥用指针的时候系统能发现么?

对宇宙最严谨的描述应该就是宇宙其实是不严谨的
2008-11-24 09:57
lingluoz
Rank: 2
来 自:苏州科技学院
等 级:新手上路
威 望:4
帖 子:749
专家分:0
注 册:2008-2-2
得分:0 
dos下面如果我没有记错NULL指针指向的地方 如果段寄存器是0000的话那么 就是int0x00中断向量的位置,你可以随意更改,通常人们用这个来捕获除0异常
dos不是保护模式的。所以没有内存非法访问这个异常

Murphy's Law :
If there are two or more ways to do something, and one of those ways can result in a catastrophe, then someone will do it.
2008-11-24 12:45
lingluoz
Rank: 2
来 自:苏州科技学院
等 级:新手上路
威 望:4
帖 子:749
专家分:0
注 册:2008-2-2
得分:0 
以下是引用风居住的街道在2008-11-24 07:02的发言:

至于原因,是不是说写NULL这个操作是通过寄存器完成的,而在FILTER里面无法改变主程序的寄存器使用?


013320A6  mov         eax,dword ptr [_p (133717Ch)]
013320AB  mov         dword ptr [eax],0

*p=0;的反汇编就是这个 (我是用vc++2008express)
出一个异常的语句就是013320AB这里其中eax=0
然后异常处理程序把dword ptr [_p (133717Ch)] 给&i了,但是eax还是0,而ExceptionFilter返回-1就是跳转到013320AB的地方重新执行。所以结果还是错误的。
本大人在刚开始写这篇文章的时候也直接把这个问题忽略掉了,后来才发现是死循环所以改用longjmp了

Murphy's Law :
If there are two or more ways to do something, and one of those ways can result in a catastrophe, then someone will do it.
2008-11-24 13:02
lingluoz
Rank: 2
来 自:苏州科技学院
等 级:新手上路
威 望:4
帖 子:749
专家分:0
注 册:2008-2-2
得分:0 
自己帮自己顶一下

Murphy's Law :
If there are two or more ways to do something, and one of those ways can result in a catastrophe, then someone will do it.
2008-11-24 23:56



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-245925-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.173058 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved