我正在为我在C ++中工作的一些代码编写这个错误处理程序。我希望能够对堆栈中的任何内容进行某种引用,而不是明确地传递给我。具体来说,让我们说我想按顺序打印调用堆栈上函数的名称。这在像JVM这样的托管运行时环境中是微不足道的,可能不是那么简单的“简单'编译代码。我可以这样做吗?
备注:
更新
我不能相信你需要向后弯腰多少才能做到这一点......几乎让我松了另一个language,这是不可提及的。
答案 0 :(得分:2)
有一种方法可以在C ++中获得反向跟踪,尽管它不可移植。我不能代表Windows,但在类Unix系统上有一个backtrace API,主要包含以下功能:
int backtrace(void** array, int size);
char** backtrace_symbols(void* const* array, int size);
void backtrace_symbols_fd(void* const* array, int size, int fd);
您可以在GNU网站here上找到最新的文档和示例。还有其他来源,例如OS X的this manual page等等。
请注意,使用此API获取回溯有一些问题。首先,没有文件名和行号。其次,在某些情况下甚至无法获得回溯,如果完全省略了帧指针(x86_64平台的最新GCC编译器的默认行为)。或者二进制文件没有任何调试符号。在某些系统上,您还必须在编译二进制文件时指定-rdynamic
标志(其他可能存在不良影响)。
答案 1 :(得分:1)
不幸的是,使用标准C ++没有内置的方法。您可以构建一个类系统来帮助您构建堆栈跟踪器实用程序,但是您需要在要跟踪的每个方法中放置一个特殊的宏。
我已经看到它使用下面列出的策略完成(甚至实现了部分内容):
这是一种相当严格的概念验证实现方法:
#include <iostream>
#include <list>
using namespace std;
struct stack_frame {
const char *funName;
const char *fileName;
int line;
stack_frame(const char* func, const char* file, int ln)
: funName(func), fileName(file), line(ln) {}
};
thread_local list<stack_frame> *frames = 0;
struct entry_exit {
bool delFrames;
entry_exit(const char* func, const char* file, int ln) {
if (!frames) {
frames = new list<stack_frame>();
delFrames = true;
} else {
delFrames = false;
}
frames->push_back(stack_frame(func, file, ln));
}
~entry_exit() {
frames ->pop_back();
if (delFrames) {
delete frames;
frames = 0;
}
}
};
void show_stack() {
for (list<stack_frame>::const_iterator i = frames->begin() ; i != frames->end() ; ++i) {
cerr << i->funName << " - " << i->fileName << " (" << i->line << ")" << endl;
}
}
#define FUNCTION_ENTRY entry_exit _entry_exit_(__func__, __FILE__, __LINE__);
void foo() {
FUNCTION_ENTRY;
show_stack();
}
void bar() {
FUNCTION_ENTRY;
foo();
}
void baz() {
FUNCTION_ENTRY;
bar();
}
int main() {
baz();
return 0;
}
上面的代码用C ++ 11编译并打印出来:
baz - prog.cpp (52)
bar - prog.cpp (48)
foo - prog.cpp (44)
没有该宏的函数在堆栈上是不可见的。性能关键功能不应该有这样的宏。
答案 2 :(得分:0)
这并不容易。确切的解决方案在很大程度上取决于操作系统和执行环境。
打印堆栈通常并不困难,但查找符号可能非常棘手,因为它通常意味着读取调试符号。
另一种方法是使用侵入式方法并为每个函数添加一些“我在哪里”类型代码(大概是“仅用于调试版本”):
#ifdef DEBUG
struct StackEntry
{
const char *file;
const char *func;
int line;
StackEntry(const char *f, const char *fn, int ln) : file(f), func(fn), line(ln) {}
};
std::stack<StackEntry> call_stack;
class FuncEntry
{
public:
FuncEntry(const char *file, const char *func, int line)
{
StackEntry se(file, func, line);
call_stack.push_back(se);
}
~FuncEntry()
{
call_stack.pop_back();
}
void DumpStack()
{
for(sp : call_stack)
{
cout << sp->file << ":" << sp->line << ": " << sp->func << "\n";
}
}
};
#define FUNC() FuncEntry(__FILE__, __func__, __LINE__);
#else
#define FUNC()
#endif
void somefunction()
{
FUNC();
... more code here.
}
我过去曾使用过这种技术,但我只输入了这段代码,它可能无法编译,但我认为它已经足够清楚了。一个主要的好处是你不必把它放在每个功能 - 只是“重要的”。 [您甚至可以根据不同的调试级别启用或禁用不同类型的FUNC
宏。