在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脚本,例如生成一个你必须包含的额外头文件.您必须确保每次在编译器的预处理器运行之前完成此预处理.
好吧,所有这些都不是不可能解决的,但我会把它留给我们中间的受虐狂......
C11 6.4.2.2预定义标识符
标识符
__func__应由翻译者隐式声明,就像紧跟每个函数定义的左括号一样,声明Run Code Online (Sandbox Code Playgroud)static const char __func__[] = "function-name";出现了,其中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()返回0或NULL出现错误并相应地设置errno.但请注意,大多数系统调用在出错时返回非零值.
| 归档时间: |
|
| 查看次数: |
2071 次 |
| 最近记录: |