Perl 的 Carp 模块有对应的 C 语言吗?

Posted

技术标签:

【中文标题】Perl 的 Carp 模块有对应的 C 语言吗?【英文标题】:Is there a C equivalent for Perl's Carp module? 【发布时间】:2010-12-07 21:17:04 【问题描述】:

在我用 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 的鲤鱼、呱呱、咯咯和忏悔子例程?我想从用户的角度报告错误。

如果没有,我知道 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 进程,然后写入它的标准输入 (已被重定向到管道)命令“回溯”后跟“退出”。然后它从结果中读取并以它喜欢的方式解析它。这几乎正​​是我需要做的。

【问题讨论】:

对于那些不熟悉 Perl 的人,carp 和 croak 执行示例 warn 和 die 宏所做的事情,仅使用来自当前子调用者的文件和行。咯咯和坦白做同样的事情,只是有一个完整的堆栈跟踪。 (一些细节简化) 文档地址:perldoc.perl.org/Carp.html 对于真正的多语句宏,强烈建议将它们包装在 do ... while(0) 中,以使其作为语句按预期工作。 【参考方案1】:

似乎没有什么能像 Carp 模块那样在 C 程序中使用,所以我在 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 会像这个库一样恢复为咯咯声(显示问题的完整堆栈跟踪)。

【讨论】:

注意:截至 2012-09-06,Github 链接为 404。【参考方案2】:

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

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

这听起来对你有帮助吗?

【讨论】:

【参考方案3】:

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

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

【讨论】:

这里提到的魔法是提供的调用函数是内置函数之一。 C 语言定义中没有提供等效功能的标准函数。 我的意思是“这里提到的魔法是作为 perl 内置函数之一提供的调用函数。C 语言定义中没有提供等效功能的标准函数。” @David:我知道你在说什么,很难解释 C 中的等价物——尤其是因为在标准 C 环境中没有等价物。而我的理解是,Perl 内置的“调用者”从 Perl 堆栈或相关数据中得出调用函数是什么。【参考方案4】:

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

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

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.

嗯,没有回溯,但有些东西。

【讨论】:

使用 vsnprintf() 防止缓冲区溢出。或者使用两个 fprintf() 调用 - 一个用于文件/函数/行信息,另一个作为 vfprintf() 来处理用户提供的格式。大家的代码好像忘记打印程序名了——argv[0];不过,这需要一些设置规则。 如果你想防止缓冲区溢出,最简单的方法是调用vasprintf而不是vsnprintf

以上是关于Perl 的 Carp 模块有对应的 C 语言吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 shell 脚本翻译成 Perl?

perl 5 鲤鱼的 raku 类似物是啥?

`死`时如何告诉行号?

Perl List::Util模块用法详解

如何有条件地将 C 代码片段编译到我的 Perl 模块?

可以将 perl 模块从一台 Linux 机器复制到另一台机器吗?