Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/69.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 在宏中提取函数名_C_C Preprocessor - Fatal编程技术网

C 在宏中提取函数名

C 在宏中提取函数名,c,c-preprocessor,C,C Preprocessor,在C语言中,我们经常需要运行这样的代码 if (! somefun(x, y, z)) { perror("somefun") } 是否可以创建一个宏,如下所示: #define chkerr ... chkerr(somefun(x, y, z)); 你会编译到上面吗 我已经知道我可以使用\uu VA\u ARGS\uu宏,但这需要我像这样调用它 chkerr(somefun, x, y, z) 短变体(您已经发现): 请注意,这可能会在嵌套的if/else或类

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

if (! somefun(x, y, z)) {
    perror("somefun")
}
是否可以创建一个宏,如下所示:

#define chkerr ...
chkerr(somefun(x, y, z));
你会编译到上面吗

我已经知道我可以使用
\uu VA\u ARGS\uu
宏,但这需要我像这样调用它

chkerr(somefun, x, y, z)         
短变体(您已经发现):

请注意,这可能会在嵌套的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并不是我们想要的。。。因此,您确实应该让它看起来像一个真正的函数(需要分号):

你可以这样称呼它:

chkErr(someFunction, 10, 12);
if(!function(10, 12))
{
    perror(CHK_ERR_TEXT_42);
}
如果出现错误,输出将为:

someFunction: <error text>
someFunction(10, 12): <error text>
另一个具有保留函数调用魅力的变体将打印出整个函数调用:

#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: <error text>
someFunction(10, 12): <error text>
好吧,决定你是否值得付出努力

顺便说一句,函数名和左括号之间的空白并没有消除。如果你想变得完美:

或者更好(谢谢chqrlie的提示):

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

#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));
啊,哈,这会导致这样的代码:

chkErr(someFunction, 10, 12);
if(!function(10, 12))
{
    perror(CHK_ERR_TEXT_42);
}
现在,这些宏从哪里来?嗯,预处理,记得吗?可能是perl或python脚本,例如。G生成必须包含的附加头文件。您必须确保每次在编译器的预处理器运行之前都完成此预处理

好吧,这一切都不是不可能解决的,但我会把这个留给我们当中的受虐狂

C116.4.2.2预定义标识符

标识符
\uuuu func\uuuu
应由翻译器隐式声明,就像在每个函数定义的左括号之后,声明一样

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));
#包括
#包括
#包括
#定义chkErr(exp)\
do{if(!(exp))\
fprintf(stderr,“%s:%d:在函数%s中,%s失败:%s\n”\
__文件,行,函数,exp,strerror(errno))\
}而(0)
chkerr(somefun(x,y,z));

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

不可能只提取函数名。C处理器将您传递的文本视为单个标记,您唯一的选择是使用Acacague建议的参数打印函数,或将名称作为单独的参数传递:

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

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

是的,使用丑陋、不安全的可变宏是可能的:

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

...
chkerr(somefunc, 1, 2, 3);
但这是一个非常糟糕的主意


呼吁保持理智:

如果只有带有普通
If
语句的原始代码,读者会认为“在这里,他们调用函数并执行一些基本的错误控制。好的,基本的东西。继续…”。但是在修改之后,任何阅读代码的人都会冻结,并认为“这是WTF吗?”

您永远无法编写比if语句更清晰的宏,这使得if语句优于宏

要遵循的一些规则:

  • 类似于宏的函数是危险的和不可读的。它们只能作为最后的手段
  • 避免用类似宏的函数发明自己的秘密宏语言。阅读你的代码的C程序员知道C。他们不知道你的秘密宏语言
  • “避免键入”通常是程序设计决策的一个糟糕理由。避免代码重复是一个很好的理由,但如果将其发挥到极致,会影响代码的可读性。如果您避免代码重复,同时使代码更具可读性,这是一件好事。如果您这样做了,但是代码变得不那么可读,那么很难证明这一点

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

chkerr(somefun(x, y, z));
使用宏和辅助函数:

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

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

如果
sumfun
是可以更改的内容(各种函数的名称),则如果不将函数名称传递给宏,就无法获得结果,就像使用VA\u arg时一样,如果要提取名称,您必须将其作为参数传递,因此必须使用
chkerr(somefun,x,y,z)
语法来实现您的目标。当然,你可以用
\uuu VA_ARGS\uuuu
来实现这一点:
\define chkerr(fun,…)do{if(fun(u VA_ARGS_uu))peror(u VA__uu))peror(uu)fun)}while(0)
。@Someprogrammerdude:是的,但他想在消息的名称中输入函数名。@anderkampling使用字符串化预处理器操作符更新了示例。它确实显示了带参数的完整调用,而不仅仅是函数名。这不起作用。如果他在
main
函数中调用它,那么
\uu func\uuu
将是
main
,而不是
somefun
@AndreKampling:答案以替代解决方案完成扮演挑剔先生:完美需要
isspace((未签名字符)*结束)
。您也可以使用一行:
函数名[strcspn(函数名,(\t”)]='\0';
。双精度
!!
也与示例不一致。@chqrlie感谢双感叹号-copy-pass错误,将其用于测试,因为我使用printf作为函数。无符号字符?Hm必须承认,因为C99函数名是
#define chkerr(fcall) \
    if (!fcall) { \
        perror(extract_fname(#fcall)); \
    }
const char *extract_fname(const char *fcall);