在宏内提取函数名称

mar*_*trz 23 c c-preprocessor

在C中,我们经常需要运行这样的代码

if (! somefun(x, y, z)) {
    perror("somefun")
}
Run Code Online (Sandbox Code Playgroud)

是否可以创建一个宏,使用如下:

#define chkerr ...
chkerr(somefun(x, y, z));
Run Code Online (Sandbox Code Playgroud)

会编译到上面吗?

我已经知道我可以使用__VA_ARGS__宏了,但这需要我称之为

chkerr(somefun, x, y, z)         
Run Code Online (Sandbox Code Playgroud)

Aco*_*gua 20

简短的变种(你已经发现):

#define chkErr(FUNCTION, ...)  \
    if(!FUNCTION(__VA_ARGS__)) \
    {                          \
        perror(#FUNCTION);     \
    }
Run Code Online (Sandbox Code Playgroud)

请注意,这可能会在嵌套的if/else或类似构造中带来很大问题:

if(x)
    chkErr(f, 10, 12) //;
                      //^ semicolon forgotten!
else
    chkErr(f, 12, 10);
Run Code Online (Sandbox Code Playgroud)

将编译为相当于以下代码:

if(x)
{
    if(!f(10, 12))
        perror("f");
    else if(!f, 12, 10))
        perror("f");
}
Run Code Online (Sandbox Code Playgroud)

很明显不是用if/else写的宏的意图...所以你真的应该更喜欢让它看起来像一个真正的函数(需要一个分号):

#define chkErr(FUNCTION, ...)      \
    do                             \
    {                              \
        if(!FUNCTION(__VA_ARGS__)) \
        {                          \
            perror(#FUNCTION);     \
        }                          \
    }                              \
    while(0)
Run Code Online (Sandbox Code Playgroud)

你会这样称呼它:

chkErr(someFunction, 10, 12);
Run Code Online (Sandbox Code Playgroud)

如果出现错误,输出将是:

someFunction: <error text>
Run Code Online (Sandbox Code Playgroud)

然而,这隐藏了一个事实,即一个函数实际上被调用,使得"外人"更难理解.相同的输出,不隐藏函数调用,但在函数和参数之间需要一个额外的逗号(与普通函数调用相比):

#define chkErr(FUNCTION, ARGUMENTS) \
do                                  \
{                                   \
    if(!FUNCTION ARGUMENTS)         \
    {                               \
        perror(#FUNCTION);          \
    }                               \
}                                   \
while(0)

chkErr(someFunction,(12, 10));
//                 ^ (!)
Run Code Online (Sandbox Code Playgroud)

另一个具有保留函数调用功能的变体将打印出整个函数调用:

#define chkErr(FUNCTION_CALL)   \
do                              \
{                               \
    if(!FUNCTION_CALL)          \
    {                           \
        perror(#FUNCTION_CALL); \
    }                           \
}                               \
while(0)

chkErr(someFunction(10, 12));
Run Code Online (Sandbox Code Playgroud)

如果出现错误,输出将是:

someFunction(10, 12): <error text>
Run Code Online (Sandbox Code Playgroud)

附录:如果你真的想准确的输出如问题显示仍然有函数调用保留(不逗号之间),你有麻烦了一点.实际上,它可能的,但它需要一些额外的工作:

问题是预处理器如何对宏参数进行操作:每个参数都是一个标记.它可以很容易地组合令牌,但不能拆分它们.

省略任何逗号会导致宏接受一个令牌,就像在我的第二个变体中一样.当然,你可以像我一样对它进行字符串化,但是你得到函数参数.这是一个字符串文字,由于预处理器无法修改字符串文字,因此必须在运行时对它们进行操作.

接下来的问题是,字符串文字是不可修改的.所以你需要修改副本!

以下变体将为您完成所有这些工作:

#define chkErr(FUNCTION_CALL)                                 \
do                                                            \
{                                                             \
    if(!FUNCTION_CALL)                                        \
    {                                                         \
        char function_name[] = #FUNCTION_CALL;                \
        char* function_name_end = strchr(function_name, '('); \
        if(function_name_end)                                 \
            *function_name_end = 0;                           \
        perror(function_name);                                \
    }                                                         \
}                                                             \
while(0)
Run Code Online (Sandbox Code Playgroud)

那么,决定你是否值得努力......

顺便说一句 - 函数名和左括号之间的空格不会被消除.如果你想要完美:

unsigned char* end = (unsigned char*) function_name;
while(*end && *end != '(' && !isspace(*end))
    ++end;
*end = 0;
Run Code Online (Sandbox Code Playgroud)

或者,更好(感谢chqrlie提示):

function_name[strcspn(function_name, "( \t")] = 0;
Run Code Online (Sandbox Code Playgroud)

我能想到的任何其他事情都需要额外的预处理步骤:

#define CAT(X, Y) CAT_(X, Y)
#define CAT_(X, Y) X ## Y

#define chkErr(FUNCTION_CALL)                 \
do                                            \
{                                             \
    if(!FUNCTION_CALL)                        \
    {                                         \
        perror(CAT(CHK_ERR_TEXT_, __LINE__)); \
    }                                         \
}                                             \
while 0

chkErr(function(10, 12));
Run Code Online (Sandbox Code Playgroud)

啊,呵呵,这会产生这样的代码:

if(!function(10, 12))
{
    perror(CHK_ERR_TEXT_42);
}
Run Code Online (Sandbox Code Playgroud)

现在,从哪里获取这些宏?那么,预处理,还记得吗?可能是perl或python脚本,例如生成一个你必须包含的额外头文件.您必须确保每次在编译器的预处理器运行之前完成此预处理.

好吧,所有这些都不是不可能解决的,但我会把它留给我们中间的受虐狂......


chq*_*lie 8

C11 6.4.2.2预定义标识符

标识符__func__应由翻译者隐式声明,就像紧跟每个函数定义的左括号一样,声明

static const char __func__[] = "function-name";
Run Code Online (Sandbox Code Playgroud)

出现了,其中function-name是词法封闭函数的名称.

你可以这样使用它:

#define chkErr(exp)  do { if (!(exp)) perror(__func__); } while (0)

chkerr(somefun(x, y, z));
Run Code Online (Sandbox Code Playgroud)

不幸的是,这会产生一个带有调用函数名称的错误消息,而不是somefun.这是一个应该工作的简单变体,甚至可以生成更多信息性的错误消息:

#define chkErr(exp)  do { if (!(exp)) perror(#exp); } while (0)

chkerr(somefun(x, y, z));
Run Code Online (Sandbox Code Playgroud)

如果somefun(x, y, z)返回非零值,则错误消息将包含字符串"somefun(x, y, z)".

您可以结合使用这两种技术来同时提供有问题的呼叫和位置:

#include <errno.h>
#include <stdio.h>
#include <string.h>

#define chkErr(exp)  \
    do { if (!(exp)) \
        fprintf(stderr, "%s:%d: in function %s, %s failed: %s\n",\
                __FILE__, __LINE__, __func__, #exp, strerror(errno)); \
    } while (0)

chkerr(somefun(x, y, z));
Run Code Online (Sandbox Code Playgroud)

这假设somefun()返回0NULL出现错误并相应地设置errno.但请注意,大多数系统调用在出错时返回非零值.