在C中,我们经常需要运行这样的代码
if (! somefun(x, y, z)) {
perror("somefun")
}
是否可以创建一个宏,使用如下:
#define chkerr ...
chkerr(somefun(x, y, z));
会编译到上面吗?
我已经知道我可以使用__VA_ARGS__
宏,但这需要我将其称为
chkerr(somefun, x, y, z)
答案 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()
在出现错误时返回0
或NULL
,并相应地设置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语句优于宏。
要遵循的一些规则:
答案 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));