Perl的Carp模块是否有对应的C语言版本?

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、croak、cluck和confess子例程Carp的存在?我想要有一些能够从用户角度报告错误的东西。
如果没有,我知道在glibc中有backtrace()和backtrace_symbols()函数,再加上-gcc选项可以提供函数名称和代码地址的回溯。但是我想要更好的东西;像Perl的caller子例程一样,能够访问调用堆栈中的文件、行和函数名称。有了这个,我就能编写自己的libcarp,用于我的c程序。
编辑:2009-10-19
我正在考虑创建一个使用可用的gdb的东西,并在basename(argv[0])上处理堆栈跟踪以生成我想要的不同类型的消息。它应该能够确定我是否不在可调试的可执行文件中或在没有gdb的系统中,在这种情况下,carp和cluck将变成警告,而craok和confess将变成死机。
我以前从未像这样使用gdb(我只在程序开始运行时运行过它,而不是在其已经运行时)。但我在glib中找到了一些函数(g_on_error_stack_trace和stack_trace),它们看起来非常接近我想要做的事情:它使用basename(argv [0])和进程ID作为参数分叉一个gdb进程,然后写入其stdin(已重定向到管道)命令“backtrace”,后跟“quit”。然后它从结果中读取并解析它所喜欢的方式。这几乎正是我需要做的事情。

11
对于不熟悉Perl语言的人来说,carp和croak与例子中的warn和die宏的作用相同,只不过能够显示当前子程序调用者的文件和行号。而cluck和confess也是这样,只不过还能显示完整的堆栈跟踪。(有些细节被简化了) - ysth
4
Perl的文档在这里:http://perldoc.perl.org/Carp.html。 - Ether
4
对于真正的多语句宏,强烈建议使用 do { ... } while(0) 进行包装,以确保它们能够按预期作为语句使用。 - unwind
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.

好的,没有回溯,但还是有所收获。


2
使用vsnprintf()来防止缓冲区溢出。或者使用两个fprintf()调用-一个用于文件/函数/行信息,另一个作为vfprintf()来处理用户提供的格式。似乎每个人的代码都忘了打印程序名称- argv [0];然而,这需要一些设置纪律。 - Jonathan Leffler
1
如果你想防止缓冲区溢出,最简单的方法是调用 vasprintf 而不是 vsnprintf - user181548

1
但是我想要更好一点的东西,可以访问调用堆栈中的文件、行和函数名称,就像 Perl 的 caller subroutine 一样。
问题在于这需要程序员决定库代码和“调用者”子程序之间的边界出现的位置。Perl 使用了一些魔法(也就是启发式方法)来实现这一点;你可以使用回溯函数来实现相同的功能。但是一般来说这并不容易。

1
这里所指的魔法是调用方函数,该函数是内置函数之一。在C语言定义中没有标准函数提供等效功能。 - David Harris
我指的是,“这里所提到的魔法是指调用函数,它作为Perl内置函数之一提供。在C语言定义中没有标准函数提供相同的功能。” - David Harris
1
@David:我知道你的意思,很难解释在C语言中等价的是什么,这主要是因为在标准的C环境中并没有相应的功能。我的理解是,Perl内置的'caller'函数可以从Perl堆栈或相关数据中推断出调用函数。 - Jonathan Leffler

1

似乎没有像Carp模块一样适用于C程序的,因此我写了一个小型库来实现这个功能github

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

warn, die
carp, croak
cluck, confess

我已经添加了前面的e变量,以便在警告中添加errno字符串,因为我认为这很有用:

ewarn, edie
ecarp, ecroak
ecluck, econfess

例如,如果您正在编写一个库并想要抱怨一个问题,只需使用:
carp("%d is not a Fibonacci number!", 54);

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

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


注意:截至2012年9月6日,Github链接已失效。 - Jonathan Leffler

0

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

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

这听起来像是可能对您有所帮助的东西吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接