C 在宏中提取函数名
在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或类
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);