• C++异常处理机制几种方法


    一、异常

      迄今为止,我们处理程序中的错误一般都是用if语句测试某个表达式,然后处理错误的特定义代码。 

     

     

    C++异常机制使用了三个新的关键字  (SEH(结构化异常处理))

    try    ──标识可能出现的异常代码段

    throw  ──抛出一个异常

    catch  ──标识处理异常的代码段

     

    提示:

     使用异常处理将带来更多的系统开销。因此慎用异常。

    二、抛出异常

    throw

    throw必须在 try代码块中.后边跟的值决定抛出异常的类型。

    三、捕获异常

    catch  

    出现在try代码块后,后边跟的数据决定捕获的类型

    catch(...) //表示捕获所有异常

    int _tmain(int argc, _TCHAR* argv[])

         int a,b;

         a=333;

         b=0;

         try

         {

             if (b==0)

             {

                 //错误处理

                 throw "出错,除数为0了";

                // throw 111.0;

             }

             printf("%d",a/b);

         }

         catch(char *s)

         {

           //错误处理

         }

         catch (int i)

         {

             //整型错误代码 处理

         }

         catch(...)

         {

             //所有异常类型

         }

         

        getchar();

        return 0;

    }

    C++ - C++ signal的使用

    1. 头文件
    #include <signal.h>


    2. 功能
    设置某一信号的对应动作


    3. 函数原型
    void (*signal(int signum,void(* handler)(int)))(int);

       分解来看:
       typedef void (*sig_t) (int);
       sig_t signal(int sig, sig_t func);
       第一个参数是目标信号。func参数是一个指针,指向某个处理该信号的函数。这个处理信号函数带有一个int型参数,并应返回void
       func参数也可以设定为下面的一些值:
       SIG_IGN: 如果func参数被设置为SIG_IGN,该信号将被忽略。
       SIG_DFL: 如果func参数被设置为SIG_DFL,该信号会按照确定行为处理。


    4. sig信号的可能类型
    1) #define SIGHUP 1 /* hangup */
       SIGHUPUnix系统管理员很常用的一个信号。许多后台服务进程在接受到该信号后将会重新读取它们的配置文件。然而,该信号的实际功能是通知进程它的控制终端被断开。缺省行为是终止进程。


    2) #define SIGINT 2 /* interrupt */
       对于Unix使用者来说,SIGINT是另外一个常用的信号。许多shellCTRL-C组合使得这个信号被大家所熟知。该信号的正式名字是中断信号。缺省行为是终止进程。

    3) #define SIGQUIT 3 /* quit */

       SIGQUIT信号被用于接收shellCTRL-/组合。另外,它还用于告知进程退出。这是一个常用信号,用来通知应用程序从容的(译注:即在结束前执行一些退出动作)关闭。缺省行为是终止进程,并且创建一个核心转储。


    4) #define SIGILL 4 /* illegal instr. (not reset when caught) */
       如果正在执行的进程中包含非法指令,操作系统将向该进程发送SIGILL信号。如果你的程序使用了线程,或者pointer functions,那么可能的话可以尝试捕获该信号来协助调试。([color=Red]注意:原文这句为:“If your program makes use of use of threads, or pointer functions, try to catch this signal if possible for aid in debugging.”。中间的两个use of use of,不知是原书排版的瑕疵还是我确实没有明白其意义;另外,偶经常听说functions pointer,对于pointer functionsgoogle了一下,应该是fortran里面的东西,不管怎样,还真不知道,确切含义还请知道的兄弟斧正。[/color])缺省行为是终止进程,并且创建一个核心转储。


    5) #define SIGTRAP 5 /* trace trap (not reset when caught) */
       SIGTRAP这个信号是由POSIX标准定义的,用于调试目的。当被调试进程接收到该信号时,就意味着它到达了某一个调试断点。一旦这个信号被交付,被调试的进程就会停止,并且它的父进程将接到通知。缺省行为是终止进程,并且创建一个核心转储。


    6) #define SIGABRT 6 /* abort() */
       SIGABRT提供了一种在异常终止(abort)一个进程的同时创建一个核心转储的方法。然而如果该信号被捕获,并且信号处理句柄没有返回,那么进程不会终止。缺省行为是终止进程,并且创建一个核心转储。


    7) #define SIGFPE 8 /* floating point exception */
       当进程发生一个浮点错误时,SIGFPE信号被发送给该进程。对于那些处理复杂数学运算的程序,一般会建议你捕获该信号。缺省行为是终止进程,并且创建一个核心转储。


    8) #define SIGKILL 9 /* kill (cannot be caught or ignored) */
       SIGKILL是这些信号中最难对付的一个。正如你在它旁边的注释中看到的那样,这个信号不能被捕获或忽略。一旦该信号被交付给一个进程,那么这个进程就会终止。然而,会有一些极少数情况SIGKILL不会终止进程。这些罕见的情形在处理一个非中断操作(比如磁盘I/O)的时候发生。虽然这样的情形极少发生,然而一旦发生的话,会造成进程死锁。唯一结束进程的办法就只有重新启动了。缺省行为是终止进程。


    9) #define SIGBUS 10 /* bus error */
       如同它的名字暗示的那样,CPU检测到数据总线上的错误时将产生SIGBUS信号。当程序尝试去访问一个没有正确对齐的内存地址时就会产生该信号。缺省行为是终止进程,并且创建一个核心转储。

    10) #define SIGSEGV 11 /* segmentation violation */
       SIGSEGV是另一个C/C++程序员很熟悉的信号。当程序没有权利访问一个受保护的内存地址时,或者访问无效的虚拟内存地址(脏指针,dirty pointers,译注:由于没有和后备存储器中内容进行同步而造成。关于野指针,可以参见http://en.wikipedia.org/wiki/Wild_pointer 的解释。)时,会产生这个信号。缺省行为是终止进程,并且创建一个核心转储。


    11) #define SIGSYS 12 /* non-existent system call invoked */
       SIGSYS信号会在进程执行一个不存在的系统调用时被交付。操作系统会交付该信号,并且进程会被终止。缺省行为是终止进程,并且创建一个核心转储。


    12) #define SIGPIPE 13 /* write on a pipe with no one to read it */
       管道的作用就像电话一样,允许进程之间的通信。如果进程尝试对管道执行写操作,然而管道的另一边却没有回应者时,操作系统会将SIGPIPE信号交付给这个讨厌的进程(这里就是那个打算写入的进程)。缺省行为是终止进程。


    13) #define SIGALRM 14 /* alarm clock */
       在进程的计时器到期的时候,SIGALRM信号会被交付(delivered)给进程。这些计时器由本章后面将会提及
    setitimeralarm调用设置。缺省行为是终止进程。


    14) #define SIGTERM 15 /* software termination signal from kill */
       SIGTERM信号被发送给进程,通知该进程是时候终止了,并且在终止之前做一些清理活动。SIGTERM信号是Unixkill命令发送的缺省信号,同时也是操作系统关闭时向进程发送的缺省信号。缺省行为是终止进程。


    15) #define SIGURG 16 /* urgent condition on IO channel */
       在进程已打开的套接字上发生某些情况时,SIGURG将被发送给该进程。如果进程不捕获这个信号的话,那么将被丢弃。缺省行为是丢弃这个信号。


    16) #define SIGSTOP 17 /* sendable stop signal not from tty */
       本信号不能被捕获或忽略。一旦进程接收到SIGSTOP信号,它会立即停止(stop),直到接收到另一个SIGCONT
    信号为止。缺省行为是停止进程,直到接收到一个SIGCONT信号为止。


    17) #define SIGTSTP 18 /* stop signal from tty */
       SIGSTPSIGSTOP类似,它们的区别在于SIGSTP信号可以被捕获或忽略。当shell从键盘接收到CTRL-Z的时候就会交付(deliver)这个信号给进程。缺省行为是停止进程,直到接收到一个SIGCONT信号为止。


    18) #define SIGCONT 19 /* continue a stopped process */
       SIGCONT也是一个有意思的信号。如前所述,当进程停止的时候,这个信号用来告诉进程恢复运行。该信号的有趣的地方在于:它不能被忽略或阻塞,但可以被捕获。这样做很有意义:因为进程大概不愿意忽略或阻塞SIGCONT信号,否则,如果进程接收到SIGSTOPSIGSTP的时候该怎么办?缺省行为是丢弃该信号。


    19) #define SIGCHLD 20 /* to parent on child stop or exit */
       SIGCHLD是由Berkeley Unix引入的,并且比SRV 4 Unix上的实现有更好的接口。(如果信号是一个没有追溯能力的过程(not a retroactive process),那么BSDSIGCHID信号实现会比较好。在system V Unix的实现中,如果进程要求捕获该信号,操作系统会检查是否存在有任何未完成的子进程(这些子进程是已经退出exit)的子进程,并且在等待调用wait的父进程收集它们的状态)。如果子进程退出的时候附带有一些终止信息(terminating information),那么信号处理句柄就会被调用。所以,仅仅要求捕获这个信号会导致信号处理句柄被调用(译注:即是上面说的信号的追溯能力”),而这是却一种相当混乱的状况。)一旦一个进程的子进程状态发生改变,SIGCHLD信号就会被发送给该进程。就像我在前面章节提到的,父进程虽然可以fork出子进程,但没有必要等待子进程退出。一般来说这是不太好的,因为这样的话,一旦进程退出就可能会变成一个僵尸进程。可是如果父进程捕获SIGCHLD信号的话,它就可以使用wait系列调用中的某一个去收集子进程状态,或者判断发生了什么事情。当发送SIGSTOP,SIGSTPSIGCONF信号给子进程时,SIGCHLD信号也会被发送给父进程。缺省行为是丢弃该信号。


    20) #define SIGTTIN 21 /* to readers pgrp upon background tty read */
       当一个后台进程尝试进行一个读操作时,SIGTTIN信号被发送给该进程。进程将会阻塞直到接收到SIGCONT信号为止。缺省行为是停止进程,直到接收到SIGCONT信号。


    21) #define SIGTTOU 22 /* like TTIN if (tp->t_local<OSTOP) */
       SIGTTOU信号与SIGTTIN很相似,不同之处在于SIGTTOU信号是由于后台进程尝试对一个设置了TOSTOP属性的tty执行写操作时才会产生。然而,如果tty没有设置这个属性,SIGTTOU就不会被发送。缺省行为是停止进程,直到接收到SIGCONT信号。


    22) #define SIGIO 23 /* input/output possible signal */
       如果进程在一个文件描述符上有I/O操作的话,SIGIO信号将被发送给这个进程。进程可以通过fcntl调用来设置。缺省行为是丢弃该信号。


    23) #define SIGXCPU 24 /* exceeded CPU time limit */
       如果一旦进程超出了它可以使用的CPU限制(CPU limit),SIGXCPU信号就被发送给它。这个限制可以使用随后讨论的setrlimit设置。缺省行为是终止进程。


    24) #define SIGXFSZ 25 /* exceeded file size limit */
       如果一旦进程超出了它可以使用的文件大小限制,SIGXFSZ信号就被发送给它。稍后我们会继续讨论这个信号。缺省行为是终止进程。


    25) #define SIGVTALRM 26 /* virtual time alarm */
       如果一旦进程超过了它设定的虚拟计时器计数时,SIGVTALRM信号就被发送给它。缺省行为是终止进程。


    26) #define SIGPROF 27 /* profiling time alarm */
       当设置了计时器时,SIGPROF是另一个将会发送给进程的信号。缺省行为是终止进程。

    27) #define SIGWINCH 28 /* window size changes */
       当进程调整了终端的行或列时(比如增大你的xterm的尺寸),SIGWINCH信号被发送给该进程。缺省行为是丢弃该信号。


    28) #define SIGUSR1 29 /* user defined signal 1 */
    29) #define SIGUSR2 30 /* user defined signal 2 */
       SIGUSR1SIGUSR2这两个信号被设计为用户指定。它们可以被设定来完成你的任何需要。换句话说,操作系统没有任何行为与这两个信号关联。缺省行为是终止进程。(译注:按原文的意思翻译出来似乎这两句话有点矛盾。)

    5. 例子
       5.1. Linux下的Ctrl+CWindows下的实现一
       Linux下通常的做法:
       signal(SIGINT, sigfunc); // 设置信号
       void sigfunc(int signo)
       {
          ... //处理信号相关的操作
       }

       以下是Linux下的Ctrl+CWindows下的实现
       #include <stdio.h>
       #include <windows.h>
       static is_loop = 1;
       // 捕获控制台 Ctrl+C 事件的函数
       BOOL CtrlHandler( DWORD fdwCtrlType )
       {
          switch (fdwCtrlType)
          {
          /* Handle the CTRL-C signal. */
          case CTRL_C_EVENT:
             printf("CTRL_C_EVENT  ");
             break;
          case CTRL_CLOSE_EVENT:
             printf("CTRL_CLOSE_EVENT  ");
             break;
          case CTRL_BREAK_EVENT:
             printf("CTRL_BREAK_EVENT  ");
             break;
          case CTRL_LOGOFF_EVENT:
             printf("CTRL_LOGOFF_EVENT  ");
             break;
          case CTRL_SHUTDOWN_EVENT:
             printf("CTRL_SHUTDOWN_EVENT  ");
             break;
          default:
             return FALSE;
          }
          is_loop = 0;
          return (TRUE);
       }


       int main(int argc, char *argv[])
       {
          printf("Set Console Ctrl Handler ");
          SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
          while (is_loop);
          return 0;
       }


       5.2.Linux下的Ctrl+CWindows下的实现二
       #include <stdio.h>
       #include <windows.h>
       #define CONTRL_C_HANDLE() signal(3, exit)
       int main(int argc, char *argv[])
       {
          printf("Set Console Ctrl Handler ");
          CONTRL_C_HANDLE();
          while (1);
          system("PAUSE");
          return 0;
       }

    windows异常处理 __try __except

    try-except用法

      try exceptwindows 系统独有的异常处理模型,windows的异常处理模式,称为SEH( structured exception handling )

           SEH的异常处理模型主要由try-except语句来完成,与标准的try catch相似。与C++异常处理模型使用catch关键字来定义异常处理模块,而SEH是采用__except关键

    字来定义。并且,catch关键字后面往往好像接受一个函数参数一样,可以是各种类型的异常数据对象;但是__except关键字则不同,它后面跟的却是一个表达式.

    我们知道,函数调用也是一个表达式。

        我们来看下面这个例子,这个例子是用来处理栈溢出的异常。    

    long WINAPI FilterFunc(DWORD dwExceptionCode)

    {

    return (dwExceptionCode == STATUS_STACK_OVERFLOW) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;

    }

    UINT WINAPI ThreadFunc(LPVOID param)

    {

    __try

    {

                       // guarded code 

    }

    __except (FilterFunc(GetExceptionCode()))

    {

    // 如果是栈溢出,进行处理。

    }

     

        return  TRUEt;    

    }

    except参数的值有以下三种:

           EXCEPTION_CONTINUE_EXECUTION (–1)     异常被忽略,控制流将在异常出现的点之后,继续恢复运行。

      EXCEPTION_CONTINUE_SEARCH (0)          异常不被识别,也即当前的这个__except模块不是这个异常错误所对应的正确的异常处理模块。系统将继续到上一try-

    except域中继续查找一个恰当的__except模块。

      EXCEPTION_EXECUTE_HANDLER (1)         异常已经被识别,控制流将进入到__except模块中运行异常处理代码

    try-except的关键是如何在__except模块中获得异常错误的相关信息.

    Windows提供了两个API函数来获取异常信息:

    LPEXCEPTION_POINTERS GetExceptionInformation(VOID); //取得异常相关信息

    DWORD GetExceptionCode(VOID); // 取得异常编号

    GetExceptionCode()返回异常编号,而GetExceptionInformation()返回更丰富的信息,EXCEPTION_POINTERS结构如下,

    typedef struct _EXCEPTION_POINTERS { // exp 

    PEXCEPTION_RECORD ExceptionRecord; 

    PCONTEXT ContextRecord; 

    } EXCEPTION_POINTERS;

    其中EXCEPTION_RECORD类型,它记录了一些与异常相关的信息;而CONTEXT数据结构体中记录了异常发生时,线程当时的上下文环境,主要包括寄存器的值。

    有了这些信息,__except模块便可以对异常错误进行很好的分类和恢复处理,通常我们需要一个过滤函数来辅助。一般称为是filterfunction.过滤函数只过滤需要处

    理的异常。

    int exception_access_violation_filter(LPEXCEPTION_POINTERS p_exinfo)

    {

        if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)

        {

            messagebox("access vialation exceptionn");

           return EXCEPTION_EXECUTE_HANDLER ; //告诉except处理这个异常

        }

        else return EXCEPTION_CONTINUE_SEARCH; //不告诉except处理这个异常

    }

    int exception_int_divide_by_zero_filter(LPEXCEPTION_POINTERS p_exinfo)

    {

        if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)

        {

            return  EXCEPTION_EXECUTE_HANDLER; //告诉except处理这个异常

        }

       else return EXCEPTION_CONTINUE_SEARCH; //不告诉except处理这个异常

    }

    于是,你可以这样写这段异常处理代码:

    __try

    {

      // guarded code

    }

    __except(exception_access_violation_filter(GetExceptionInformation()))

    {

    //

    }

    __try

    {

      // guarded code

    }

    __exceptexception_int_divide_by_zero_filter(GetExceptionInformation()))

    {

    //exception handling

    }

    SEH异常处理模型中,也可以抛出一个异常。对应的WindowsAPI函数是RaiseException,

    VOID RaiseException(

    DWORD dwExceptionCode, // 异常的编号

    DWORD dwExceptionFlags, // 异常标记

    DWORD nNumberOfArguments, // 参数个数

    CONST DWORD *lpArguments //  参数数组首地址

    );

    通常,后三个参数基本不用

    SEH异常处理还有try-finally.类似于java里的try-catch-finally.但是SEHtry只能和exceptfinally两者之间的一个搭配,不能有try-except-finnaly. 

     C++异常模型用try-catch语法定义,而SEH异常模型则用try-except语法,C++异常模型相似,try-except也支持多层的try-except嵌套。

     try-except模型中,一个try块只能是有一个except块;而C++异常模型中,一个try块可以有多个catch块。

     C++异常模型是按照异常对象的类型来进行匹配查找的;而try-except模型则不同,它通过一个表达式的值来进行判断.

      __except关键字后面跟的表达式,它可以是各种类型的表达式,例如,它可以是一个函数调用,或是一个条件表达式,或是一个逗号表达式,或干脆就是一个整

    型常量等等。最常用的是一个函数表达式,并且通过利用GetExceptionCode()GetExceptionInformation ()函数来获取当前的异常错误信息,便于程序员有效控制异常

    错误的分类处理。

    SEH异常处理模型中,异常通过RaiseException()函数抛出。RaiseException()函数的作用类似于C++异常模型中的throw

    关于SEH异常处理更详细的资料,你可以去看windows via c/c++这本书,中文译名是windows核心编程。不过还是建议你看英文原版,翻译的版本质量不高。

    C语言中出现内存不可读写错误,如何不让这个异常终止程序?

    最后使用 __try __exception 捕获了这个异常。

    __try

    {

    }

    __exception(EXCEPTION_EXECUTE_HANDLER)

    {

    }


    error C2712: 无法在要求对象展开的函数中使用 __try

    [cpp] view plain
    1. bool WindowContainer::GotoMainPage(bool bDestroyCurWndPage/* = true*/)  
    2. {  
    3.     bool bResult = false;  
    4.   
    5.     LockChilds();  
    6.   
    7.     __try  
    8.     {  
    9.         <span style="color:#cc0000;">GUIList::iterator it = m_lstWndPage.begin();// 这句话报错</span><span style="color:#cc0000;">消息                 </span>  
    [cpp] view plain
    1. <span style="color:#cc0000;">                   </span><span style="color:#006600;">if(it != m_lstWndPage.end() && (*it) != m_pCurPage)  
    2.         {  
    3.             WndPage * pCurPage = m_pCurPage;  
    4.   
    5.             SwitchToWndPage((WndPage *)*it, 0, 0);  
    6.   
    7.             bDestroyCurWndPage ? DestroyWndPage(pCurPage) : 0;  
    8.         }  
    9. </span> }  
    10.     __except(EXCEPTION_EXECUTE_HANDLER)  
    11.     {  
    12.         printf(" WindowContainer::PopWndPage Catch Exception:%x! ", GetExceptionCode());  
    13.     }  
    14.   
    15.     UnLockChilds();  
    16.   
    17.     return bResult;  
    18. }  

    报错内容如下:

    1>.WindowContainer.cpp(576) : error C2712: 无法在要求对象展开的函数中使用 __try

    解决方法:

    错误消息

    无法在要求对象展开的函数中使用 __try

    使用 /EHsc 时,带有结构化异常处理的函数不能具有要求展开(毁坏)的对象。

    可能的解决方案:

    • 将要求 SEH 的代码移动到另一个函数中。

    • 重写使用 SEH 的函数以避免使用具有析构函数的局部变量和参数。在构造函数或析构函数中不要使用 SEH。

    • 不使用 /EHsc 进行编译。

    示例

    如果使用 /clr:pure 进行编译并在 __try 块中声明指针到函数的静态数组,则会发生 C2712 错误。静态成员要求编译器在/clr:pure 下使用动态初始化功能,这意味着 C++ 异常处理。但是,不允许在__try 块中进行 C++ 异常处理。

    下面的示例生成 C2712。

    [cpp] view plain
      1. // C2712.cpp  
      2. // compile with: /clr:pure /c  
      3. struct S1 {  
      4.    static int smf();  
      5.    void fnc();  
      6. };  
      7.   
      8. void S1::fnc() {  
      9.    __try {  
      10.       static int (*array_1[])() = {smf,};   // C2712  
      11.   
      12.       // OK  
      13.       static int (*array_2[2])();  
      14.       array_2[0] = smf;  
      15.     }  
      16.     __except(0) {}  
      17. }  

     

  • 相关阅读:
    mybatis-plus代码生成模板
    Flask_APScheduler的简单使用
    Linux 配置mysql 远程连接
    ubuntu19.04 安装mysql,没有初始密码,重设初始密码
    ubuntu19.04 配置远程连接ssh
    python3 win 建立虚拟环境(virtualenv)
    python property(不动产)方法
    python,装饰器带参数,原理
    利用python装饰器为字符串添加,HTML标签
    python pymysql 基本使用
  • 原文地址:https://www.cnblogs.com/kuangke/p/5371128.html
Copyright © 2020-2023  润新知