21xrx.com
2024-09-20 00:25:04 Friday
登录
文章检索 我的文章 写文章
C++如何打印调用栈?
2023-07-13 20:10:59 深夜i     --     --
C++ 打印 调用栈

在C++中,打印调用栈是一项非常有用的技能,它可以帮助程序员快速定位和修复代码中的错误。下面介绍几种常见的打印调用栈的方法。

1.使用Exception Handle

在C++中,可以通过使用异常处理机制来捕获Call Stack。当程序抛出异常时,会自动记录调用栈信息并将其存储在异常对象中。通过打印异常对象中的调用栈信息,可以得到调用栈的完整信息。以下是一个示例代码:


#include <iostream>

#include <exception>

using namespace std;

void func3()

{

  throw exception();

}

void func2()

{

  func3();

}

void func1()

{

  func2();

}

int main()

{

  try

  {

    func1();

  } catch(const exception& e)

  {

    cerr << "Caught exception: " << e.what() << endl;

    const char * const * stk = e.__cxa_exception->exception_caught->adjusted_exception_pointer->__cxa_exception->unexpected_handler_arg;

    cerr << "Call stack:" << endl;

    for(int i = 0; stk[i]; i++)

    {

      cerr << stk[i] << endl;

    }

  }

  return 0;

}

运行以上代码可以得到输出:


Caught exception: std::exception

Call stack:

./a.out(_Z5func3v+0x1a)[0x400b0a]

./a.out(_Z5func2v+0xe)[0x400b2e]

./a.out(_Z5func1v+0xe)[0x400b35]

./a.out(main+0x16)[0x400b44]

/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f15d7e8b830]

./a.out[0x400999]

在这个例子中,我们模拟了一个多级函数调用的过程,通过抛出异常并打印调用栈信息,我们可以看到每个函数在调用栈中的位置。其中,最后一行是程序入口main函数所在的位置。

2.使用backtrace和dladdr

另一种常用的方式是使用backtrace和dladdr来获取调用栈信息。backtrace可以获取当前线程的调用栈,而dladdr可以解析函数指针,获取函数名和函数地址。我们可以将这两个函数结合起来,打印出完整的调用栈信息。以下是一个示例代码:


#include <iostream>

#include <execinfo.h>

#include <dlfcn.h>

#include <cstring>

using namespace std;

void print_callstack()

{

  void *array[10];

  int size = backtrace(array, 10);

  char **strings = backtrace_symbols(array, size);

  for (int i = 0; i < size; i++) {

    Dl_info info;

    if (dladdr(array[i], &info) && info.dli_sname) {

      char *demangled = nullptr;

      int status;

      demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status);

      printf("Call stack: %d %s: (%s) [%p]\n", i, strings[i],

        status != 0 ? info.dli_sname : demangled ? demangled : "???", array[i]);

      if (demangled) free(demangled);

    } else {

      printf("Call stack: %d %s: [0x%p]\n", i, strings[i], array[i]);

    }

  }

  if (strings) free(strings);

}

void func3()

{

  print_callstack();

}

void func2()

{

  func3();

}

void func1()

{

  func2();

}

int main()

{

  func1();

  return 0;

}

运行以上代码可以得到输出:


Call stack: 0 ./a.out(_Z5func3v+0x1a) [0x4008da]

Call stack: 1 ./a.out(_Z5func2v+0xe) [0x4008f2]

Call stack: 2 ./a.out(_Z5func1v+0xe) [0x4008f9]

Call stack: 3 ./a.out(main+0x16) [0x40090a]

Call stack: 4 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f74aa326830]

Call stack: 5 ./a.out[0x400799]

在这个例子中,我们使用backtrace获取当前线程的调用栈信息,并使用dladdr解析出每个函数在内存中的地址和函数名。注意,由于dladdr解析的函数名可能是一个mangled name(C++编译器生成的函数名),因此需要使用abi::__cxa_demangle来将其解码成可读的字符串。

总之,C++中打印调用栈是一项非常有用的技能,对于调试代码和发现错误都非常有帮助。希望以上介绍的方法可以帮助你更好地理解和使用打印调用栈的技巧。

  
  

评论区

{{item['qq_nickname']}}
()
回复
回复