7

在我用 C 完成的一些项目中,我喜欢使用以下与 Perl 的 warn 和 die 子例程类似的宏:

#include <stdio.h>
#include <stdlib.h>

#define warn(...) \
    fprintf(stderr, __VA_ARGS__); \
    fprintf(stderr, " at %s line %d\n", __FILE__, __LINE__)

#define die(...) \
    warn(__VA_ARGS__); \
    exit(0xFF)

是否存在像 Perl 的Carp中的 carp、croak、cluck 和 admission 子例程?我想从用户的角度报告错误。

如果没有,我知道 glibc 中有 backtrace() 和 backtrace_symbols() 函数以及 -rdynamic gcc 选项可以为我提供函数名称和代码地址的回溯。但我想要更好的东西;可以访问调用堆栈中的文件、行和函数名,就像 Perl 的调用者子例程一样。有了它,我就可以编写自己的 libcarp 以在我的 c 程序中使用。

编辑:2009-10-19

我正在考虑创建一些在 basename(argv[0]) 上可用时使用 gdb 的东西,然后处理堆栈跟踪以生成我想要的不同类型的消息。它应该能够确定我是否不在可调试的可执行文件中,或者没有 gdb 的系统,在这种情况下,carp 和 cluck 将成为警告,而 craok 和承认将成为死亡。

我以前从未像这样使用过 gdb(我只在我的程序开始时运行它,而不是在它已经运行时)。但是我在 glib 中发现了一些函数(g_on_error_stack_trace 和 stack_trace),它们看起来非常接近我想要做的:它使用参数 basename(argv[0]) 和进程 id 分叉一个 gdb 进程,然后写入它的标准输入 (已被重定向到管道)命令“回溯”后跟“退出”。然后它从结果中读取并以它喜欢的方式解析它。这几乎正​​是我需要做的。

4

4 回答 4

1

好吧,我从未尝试过显示调用堆栈,但是对于我的程序,我曾经执行以下操作。

首先,我定义了一个执行实际日志记录的函数。这只是一个例子;请注意,此功能非常不安全(缓冲区溢出任何人?)

void strLog(char *file, char *function, int line, char *fmt, ...)
{
     char buf[1024];
     va_list args;

     va_start(args, fmt);
     vsprintf(buf, fmt, args);
     va_end(args);

     fprintf(stderr, "%s:%s:%d:%s\n", file, function, line, buf);
}

但是,这不是很实用。实用的是用宏来调用这个函数。

#define die( ... ) \
        strLog( __FILE__, __PRETTY_FUNCTION__, \
        __LINE__, __VA_ARGS__ )

然后你可以像调用一样调用printf()

if (answer == 42) die("Oh, %d of course.", answer);

你会得到这样的东西:

main.c:10:somefunc: Oh, 42 of course.

好吧,没有回溯,但有些东西。

于 2009-10-19T12:37:11.350 回答
1

似乎没有什么能像 C 程序中使用的 Carp 模块那样存在,所以我在github 上编写了一个小型库来完成它。

该库定义了以下导出供使用:

warn, die
carp, croak
cluck, confess

我已经添加了以前的 e-varieties 用于将 errno 字符串添加到警告中,因为我认为它会很有用:

ewarn, edie
ecarp, ecroak
ecluck, econfess

例如,如果您正在编写一个库并想解决一个问题,只需使用

carp("%d is not a Fibonacci number!", 54);

它将显示调用库的第一个函数的文件和行号。

Perl 的 Carp 模块使用不同的包而不是文件来查找可疑的子例程。它还递归地使用@ISA 数组或@CARP_NOT 来确定哪个子例程在受信任的包组之外。我打算添加类似的东西。如果堆栈跟踪的顶部在受信任的范围内,那么 carp 将恢复为 cluck (显示问题的完整堆栈跟踪),就像这个库一样。

于 2009-10-25T21:50:51.217 回答
1

但是我想要更好的访问调用堆栈中的文件、行和函数名的东西,比如 Perl 的调用者子例程。

问题是这需要程序员的帮助来决定你的库代码和“调用者”子例程之间的边界出现在哪里。Perl 使用了一些魔法(也就是启发式算法)来做到这一点;也许你可以对回溯函数做同样的事情。但总的来说,这不是微不足道的。

于 2009-10-19T15:08:37.587 回答
0

我为我的嵌入式 C (gcc) 应用程序编写了回溯例程。如果可用,它会使用 -gstabs 信息来查找函数名称。需要注意的是,elf 文件必须在程序可以找到的某个位置,以便查看 stabs 段。在我的嵌入式应用程序中,elf 文件位于闪存中,我有一个指向它的指针。在您的情况下,您必须编写一些代码才能从磁盘读取它。

我很确定文件和行号也在 stabs 段中。

这听起来像是对你有帮助的东西吗?

于 2009-10-21T13:02:11.073 回答