C/C++代码内捕获异常发生时的调用栈

Posted 李慕清

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++代码内捕获异常发生时的调用栈相关的知识,希望对你有一定的参考价值。

在C/C++代码内捕获异常信号发出时的调用栈信息。

函数实现

#include <signal.h>
#include <execinfo.h>

static void backtrace_handler_V1(int sig)


  void *array[128];
  size_t size;

  // get void*\'s for all entries on the stack
  size = backtrace(array, 10);

  // print out all the frames to stder
  backtrace_symbols_fd(array, size, STDERR_FILENO);

  exit(1);


void backtrace_handler_V2(int sig) 
    void* callstack[128];
    int frames = backtrace(callstack, 128);
    char** strs = backtrace_symbols(callstack, frames);
    for (int i = 0; i < frames; ++i) 
        printf("%s\\n", strs[i]);
    
    free(strs);
    exit(1);


测试代码:

int main(int argc, char **argv)

    //注册捕捉总线错误时打印堆栈处理的函数
    signal(SIGBUS, backtrace_handler_V1);
    //注册捕捉段错误时打印堆栈处理的函数
    signal(SIGSEGV, backtrace_handler_V1);

    return 0;

注解:

  1. 头文件中包含 <signal.h> 和 <execinfo.h>,这两个头文件分别用于处理信号和获取调用栈信息。
  2. 在 backtrace_handler 函数中,首先声明一个指针数组,用于存储调用栈信息。然后调用 backtrace 函数,将当前线程的调用栈信息存储到 callstack 中,并返回调用栈信息的帧数。最后调用 backtrace_symbols(或 backtrace_symbols_fd直接打屏)函数,将 callstack 中的地址转换为字符串形式,并返回一个指向这些字符串的指针数组。最后遍历这个指针数组,即可将调用栈信息输出到日志文件或控制台。
  3. 如果程序在运行过程中发生了错误,程序会被 backtrace_handler 函数捕获并输出调用栈信息。

java —— 异常

  上文主要说明了异常的产生、异常的捕获、异常处理流程。程序中的异常是不可以避免的,那么应该怎么去处理异常尤为重要了。

处理异常的方式

  1、只进行捕获,不做任何处理

  

  当异常发生时,上述代码对异常进行了捕获,捕获发生后进入catch子句,catch子句里面没有任何代码,实际上异常并没有得到处理,因此catch子句执行完成后,catch区段之后的代码将“好像没有发生过什么似的”继续执行。换句话说,方式1没有对异常做任何应对,只是将它给(吞)了。这样的做法很危险,因为后续程序可能就会遭遇失败,由于缺少对上述异常的必要处理与记录,直接导致我们定位错误非常困难。

  2、捕获异常,简单的打印

   

  e.printStackTrace()进行异常情况输出,在调试可能会有一定的帮助,但是调试阶段结束后,就不利于程序的维护。更加安全有理的方式是,在屏幕上输出了异常发生的位置与导致异常的原因,同时还将异常详细信息输出到了一个日志文件。

public class ExceptionDemo {
    public static void throwsTest(){
        try {
            Class.forName("java.util.List");
        } catch (ClassNotFoundException e) {//异常处理
            System.out.println("Class.forName() 类加载异常:" + e.getMessage());
            logFile(nfe);
            
        }
    }
    public static void main(String[] args) {
        throwsTest();
    }
}

  2、throws 异常  

    throws 在方法的声明处使用,表示此方法不处理异常,所有的异常交给被调用处进行处理,而调用处也可继续向上递交异常。

public class ExceptionDemo {
    public static void throwsTest() throws ClassNotFoundException{
         Class.forName("java.util.List");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        throwsTest();
    }
}

  此处都没有对异常进行处理,最终有 JVM 都异常处理,进行后台的异常输出。

  3、throw 异常

  throw 主要用于方法体之中,表示人为的抛出一个异常类的实例化对象,常用来抛出自定义的异常,与 throws 配合使用将异常交由调用者处理。     

  

  备注:自定义异常的方式

      

   在正常使用的情况下,很少会定义自己的异常类,但是如果在学习框架,或者是你日后设计框架的时候都有可能定义许多属于此框架的异常类。 

  上述的异常,最终的处理形式还是回到了方式2的处理规则上。进行错误信息简单输出,完整信息写入日志文件。

小结:

  不要推诿或延迟处理异常,尽量就地解决他们,就地解决最好,并且需要实实在在的进行处理,而不是只捕捉,不动作。

以上是关于C/C++代码内捕获异常发生时的调用栈的主要内容,如果未能解决你的问题,请参考以下文章

Java异常如何解决

当try块中有多个异常发生时,如何处理?

liteos 异常接管(十五)

java —— 异常

python(异常处理)

异常处理模块包时间模块subprocess(调用shell命令)