10

我正在使用 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 函数。有一些方法可以获取当前函数以及调用函数,但这些方法都不能从函数外部工作。

4

4 回答 4

9

你可以有这个

#define SAFE_CALL(func) \
do {\
    if (errorCode = (func)) \
        printError(errorCode, #func, __LINE__, __FILE__);\
} while(0)

SAFE_CALL(functionName(params));
于 2013-05-16T00:48:32.240 回答
5

这里的解决方案是创建一个调用您的函数的宏,如果它失败,则使用您的特殊参数调用一个打印函数。如果您使用的是 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 扩展(clang 也支持),如果没有提供可变参数,则可以正确传递可变参数。

然后可以这样使用:

checkError(fail(), "Couldn't validate results: %s", "Context-Sensitive Debug Info");

它输出这个可爱的文本,供您调试乐趣:

函数错误在文件 /Users/rross/Documents/TestProj/TestProj/main.mm 的第 703 行失败:无法验证结果:上下文相关的调试信息

您可能想尝试使用其他宏,例如 GCC/Clang's __PRETTY_FUNCTION__,但这应该可以帮助您入门!

于 2013-05-16T00:47:43.630 回答
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行为更像一个整数类型(如果这很重要)。

于 2013-05-16T00:49:57.777 回答
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 返回值为ints。如果它们是其他一些标量类型(或不同函数的不同标量类型),您可能必须更改它们的传递/输出方式。


原始解决方案

调用一个调用你的函数的宏绝对是这里的方法,但我建议使用三元运算符而不是控制结构。这允许您使用宏:

  • 作为一个独立的声明(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,但仍然必须在宏之外定义)。
  • 它打印用于调用该函数的确切代码。在某些(甚至大多数)情况下,这实际上可能是一件好事,但是我认为这在函数指针场景中并不理想(当然,在调用 API 时可能不常见)。此外,更难打印传递参数的实际值。这两种方法都可以在一定程度上解决,但代价是让它更笨拙和/或更不便携。
于 2013-05-16T04:58:15.387 回答