我正在使用API,我发现自己编写了许多一般形式的错误处理代码:
if (errorCode = functionName(params))
printError(errorCode, "functionName", __LINE__, __FILE__);
printError
的正文可能如下:
fprintf(stderr, "Error in function %s on line %d of file %s",
functionName, lineNumber, fileNumber);
然而,每次硬编码函数名都很痛苦。有没有办法在运行时或通过宏获取导致错误的函数名称,即最后一个名为的函数?当然,我无法以任何方式修改API函数。有一些方法可以获得current function以及calling function,但这些方法都不能在函数外部工作。
答案 0 :(得分:9)
你可以拥有这个
#define SAFE_CALL(func) \
do {\
if (errorCode = (func)) \
printError(errorCode, #func, __LINE__, __FILE__);\
} while(0)
SAFE_CALL(functionName(params));
答案 1 :(得分:5)
这里的解决方案是创建一个调用函数的宏,如果失败,则调用带有特殊参数的print函数。如果您正在使用C99,那么您可以(ab)使用可变参数宏,如下所示:
void printError(const char *fmt, const char *func, const int line, const char *file, ...) {
// the first three arguments are our function, line and file, so first, print out that first
va_list list;
va_start(list, file);
// only print until the first '('
int nchars = (int) (strchr(func, '(') - func);
fprintf(stderr, "Error in function %.*s on line %d of file %s: ", nchars, func, line, file);
vfprintf(stderr, fmt, list);
va_end(list);
}
#define checkError(invoke, fmt, ...)\
do {\
int err;\
if ((err = (invoke))) {\
printError(fmt, #invoke, __LINE__, __FILE__, ## __VA_ARGS__);\
}\
} while(0)
请注意,上面使用了GCC extension(在clang中也支持),如果没有提供可变参数,则允许正确传递变量参数。
然后可以这样使用:
checkError(fail(), "Couldn't validate results: %s", "Context-Sensitive Debug Info");
这为您的调试乐趣输出了这个可爱的文字:
Error in function fail on line 703 of file /Users/rross/Documents/TestProj/TestProj/main.mm: Couldn't validate results: Context-Sensitive Debug Info
您可能想要使用其他宏,例如GCC / Clang的__PRETTY_FUNCTION__
,但这应该可以帮助您入门!
答案 2 :(得分:2)
注意:此解决方案仅在可以修改或包装API以修改错误代码的返回类型时才有效。
我会使用返回代码的函数的名称来扩充errorCode
返回值。然后,printError()
函数可以从errorCode
本身提取信息。
typedef struct error_code_type {
unsigned code;
const char *function;
} error_code_type;
error_code_type error_code (unsigned code. const char *function) {
error_code_type x = { code, function };
return x;
}
#define ERROR_CODE(code) error_code(code, __func__)
因此,返回错误时的函数将使用:return ERROR_CODE(xyz);
。错误代码的接收者接收返回错误的函数的函数名称。如果复制错误代码而不是重新构造错误代码,则可以正确地反向传播触发错误的最内层函数。
例如:
void foo () {
error_code_type errorCode;
if ((errorCode = the_api_function()).code) {
printError(errorCode.code, errorCode.function, __LINE__, __FILE__);
}
}
如果使用C ++实现,则可以使用运算符重载使error_code_type
行为更像是一个整数类型,如果这很重要的话。
答案 3 :(得分:1)
如果您没有定义errorCode
(或者并不总是想要设置),请改用它:
#define CALL_AND_LOG_ERROR(callee) \
CheckCallAndPrintError(callee, #callee, __func__, __LINE__, __FILE__)
int CheckCallAndPrintError( int result, const char* callee,
const char* caller, int line, const char* file)
{
if(result)
{
fprintf(stderr, "Error %d returned by function %s called from function %s on line \
%d of file %s\n", result, callee, caller, line, file);
}
return result;
}
然后您可以使用CALL_AND_LOG_ERROR
,如果您想要保留值,则明确必须分配给变量:
int errorCode = CALL_AND_LOG_ERROR(SomeFunc(a, 0, NULL));
虽然这解决了对外部变量的依赖,但我不知道优化器会多长时间内联调用。我怀疑性能影响无论如何都会显着。请注意,两种解决方案都假设API返回值为int
s。如果它们是其他标量类型(或不同函数的不同标量类型),则可能必须更改它们的传递/输出方式。
调用一个调用你的函数的宏绝对是这里的方法,但我建议使用三元运算符而不是控制结构。这允许您使用宏:
errorCode
)示例代码(您可以在http://ideone.com/NG8U16处使用它):
#include <stdio.h>
// these macros rely on the fact that errorCode is defined in the the caller
#define FUNC_RETURN_ISNT(func, unexpected) \
((errorCode = func) == unexpected) ? printf("error: call %s shouldn't have returned %d but did (from function %s at line %d of file %s)\n", #func, unexpected, __func__, __LINE__, __FILE__), 0 : 1
#define FUNC_RETURN_IS(func, expected) \
((errorCode = func) != expected) ? printf("error: call %s returned %d instead of %d (from function %s at line %d of file %s)\n", #func, errorCode, expected, __func__, __LINE__, __FILE__), 0 : 1
#define ERROR_CHECK(func) \
(errorCode = func) ? printf("error: call %s returned %d (from function %s at line %d of file %s)\n", #func, errorCode, __func__, __LINE__, __FILE__), errorCode : 0
int func(int a, int b, int c)
{
return a^b^c;
}
int func2(void)
{
return -1;
}
int func3(void)
{
static int i = 3;
return i--;
}
int main(void)
{
int a = 0, b = 0, c = 0;
int errorCode;
int (*funcPoint)(void) = func2;
FUNC_RETURN_ISNT(func(1,1,1), 1);
FUNC_RETURN_IS(func(a,b,c), 1);
ERROR_CHECK(func(a,b,1));
if(ERROR_CHECK(func2()))
{
printf("func2 failed error check\n");
}
else
{
printf("func2 passed error check\n");
}
if(ERROR_CHECK(funcPoint()))
{
printf("funcPoint failed error check\n");
}
else
{
printf("funcPoint passed error check\n");
}
if(ERROR_CHECK(func(0,0,0)))
{
printf("func failed error check\n");
}
else
{
printf("func passed error check\n");
}
while(ERROR_CHECK(func3()))
{
printf("retry...\n");
}
switch(ERROR_CHECK(func(1,2,4)))
{
case 0:
printf("okay\n");
break;
case 1:
printf("non-fatal error 1\n");
break;
case 7:
printf("fatal error 7\n");
return 1;
}
return 0;
}
示例输出:
error: call func(1,1,1) shouldn't have returned 1 but did (from function main at line 38 of file prog.c)
error: call func(a,b,c) returned 0 instead of 1 (from function main at line 40 of file prog.c)
error: call func(a,b,1) returned 1 (from function main at line 42 of file prog.c)
error: call func2() returned -1 (from function main at line 44 of file prog.c)
func2 failed error check
error: call funcPoint() returned -1 (from function main at line 53 of file prog.c)
funcPoint failed error check
func passed error check
error: call func3() returned 3 (from function main at line 71 of file prog.c)
retry...
error: call func3() returned 2 (from function main at line 71 of file prog.c)
retry...
error: call func3() returned 1 (from function main at line 71 of file prog.c)
retry...
error: call func(1,2,4) returned 7 (from function main at line 76 of file prog.c)
fatal error 7
但仍存在一些缺点:
errorCode
已经定义(由于printf
,它必须是一个整数类型)。然而,公平地说,原始代码片段也是如此。可以修改宏以接受变量作为参数(它不必总是被命名为errorCode
,但它仍然必须在宏之外定义。)