在宏内提取函数名称

时间:2017-07-06 07:19:51

标签: c c-preprocessor

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

if (! somefun(x, y, z)) {
    perror("somefun")
}

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

#define chkerr ...
chkerr(somefun(x, y, z));

会编译到上面吗?

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

chkerr(somefun, x, y, z)         

5 个答案:

答案 0 :(得分:20)

短变种(你已经发现):

#define chkErr(FUNCTION, ...)  \
    if(!FUNCTION(__VA_ARGS__)) \
    {                          \
        perror(#FUNCTION);     \
    }

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

if(x)
    chkErr(f, 10, 12) //;
                      //^ semicolon forgotten!
else
    chkErr(f, 12, 10);

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

if(x)
{
    if(!f(10, 12))
        perror("f");
    else if(!f, 12, 10))
        perror("f");
}

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

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

你会这样称呼:

chkErr(someFunction, 10, 12);

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

someFunction: <error text>

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

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

chkErr(someFunction,(12, 10));
//                 ^ (!)

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

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

chkErr(someFunction(10, 12));

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

someFunction(10, 12): <error text>

附录:如果你真的希望完全,问题中显示的输出仍然保留了函数调用(之间没有逗号) ),你有点麻烦。实际上, 是可能的,但它需要一些额外的工作:

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

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

然后,下一个问题是字符串文字是不可修改的。所以你需要修改副本

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

#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)

好吧,决定你是否值得努力...

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

unsigned char* end = (unsigned char*) function_name;
while(*end && *end != '(' && !isspace(*end))
    ++end;
*end = 0;

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

function_name[strcspn(function_name, "( \t")] = 0;

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

#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));

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

if(!function(10, 12))
{
    perror(CHK_ERR_TEXT_42);
}

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

嗯,这一切都不是不可能解决的,但我会把它留给我们中间的受虐狂......

答案 1 :(得分:8)

  

C11 6.4.2.2预定义标识符

     

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

static const char __func__[] = "function-name";
     

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

您可以这样使用它:

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

chkerr(somefun(x, y, z));

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

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

chkerr(somefun(x, y, z));

如果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));

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

答案 2 :(得分:2)

是的,可能有一个丑陋,不安全的可变参数宏:

#define chkerr(func, ...) \
if(!func(__VA_ARGS__))    \
{                         \
  perror(#func);          \
}

...
chkerr(somefunc, 1, 2, 3);

但这是一个非常糟糕的主意。

要求理智:

如果只有原始代码带有普通if语句,读者会认为&#34;在这里他们调用一个函数并进行一些基本的错误控制。好的,基本的东西。继续...&#34;。但是在更改之后,任何阅读代码的人都会冻结并思考&#34; WTF就是这个???&#34;。

你永远不能写一个比if语句更清晰的宏 - 这使得if语句优于宏。

要遵循的一些规则:

  • 类似函数的宏是危险且不可读的。它们只应作为最后的手段。
  • 避免使用类似函数的宏发明自己的秘密宏语言。读过你代码的C程序员都知道C.他们不知道你的秘密宏语言。
  • &#34;避免打字&#34;对于项目设计决策而言,这通常是一个不好的理由避免代码重复是一个很好的理由,但将其置于极端会影响代码的可读性。如果你避免代码重复并使代码同时更具可读性,这是一件好事。如果你这样做但代码变得不那么可读,那就很难证明这一点。

答案 3 :(得分:2)

您可以使用原始通话格式:

chkerr(somefun(x, y, z));

使用宏和辅助函数:

#define chkerr(fcall) \
    if (!fcall) { \
        perror(extract_fname(#fcall)); \
    }
const char *extract_fname(const char *fcall);

extract_fname函数将获取文本并返回所有内容,直到打开括号。

答案 4 :(得分:1)

无法提取只是函数名称。 C处理器将您传递的文字视为单个标记,which can't be manipulated.您唯一的选择是使用Aconcague建议的参数打印函数或将名称作为单独的参数传递:

#define chkErr(FUNCTION_NAME, FUNCTION_CALL) \
if(!FUNCTION_CALL) \
{ \
    perror(#FUNCTION_NAME); \
}

chkErr(someFunction, someFunction(10, 12));