【问题标题】:Get last function called in C/C++获取在 C/C++ 中调用的最后一个函数
【发布时间】:2013-05-10 17:48:27
【问题描述】:

我正在使用 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,但这些方法都不能在函数外部工作。

【问题讨论】:

  • #define ERROR_LOCATION __func__, __LINE__, __FILE__ 和 printError(errorCode, ERROR_LOCATION); 与我通常做的类似,或者将整个调用放在一个宏中,尽管前者更灵活一些,可以为所有重载使用一个宏printError.
  • @chris 我会小心的。更好的选择是制作一个参数宏,您可以像函数一样调用它。传递三个看起来像一个的参数会导致一些......有趣的情况。
  • @RichardJ.RossIII,只要清楚它应该只用于调用需要错误源的错误函数,我看不出有任何问题。你有什么特别的想法吗?
  • @chris __func__ 给出了我当前所在的函数,但我想要我刚刚离开的 API 函数的名称。
  • @1'',我明白了,如果我把它放进去,我通常会在自定义消息中包含它。调整你使用的任何宏并不难,但我没有找到比这更好的方法用于此目的的宏。这是我将使用的少数几次之一。

标签: c++ c api function


【解决方案1】:

你可以拥有这个

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

SAFE_CALL(functionName(params));

【讨论】:

  • #func 有什么作用?与#line 不同,它不是一个独立的预处理指令。
  • @1" 将宏参数转换为字符串。
  • +1 也有同样的想法,但也有一些缺点。最大的是被调用者的成功或失败对调用者是隐藏的。如果每个错误都导致程序exit,那很好,但如果处理更加细微,这将成为一个重大障碍。
  • @jerry 您可以调整它以使其“返回”错误代码并检查您需要特别注意的功能。类似#define SAFE_CALL(func) ({if(errorCode=(func))printError(...);errorCode;})
  • 这不会“返回”任何东西,无论你如何使用它都不应该编译[尽管我以前很惊讶:)]。
【解决方案2】:

这里的解决方案是创建一个调用您的函数的宏,如果它失败,则使用您的特殊参数调用一个打印函数。如果您使用的是 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");

为了您的调试乐趣而输出这个可爱的文本:

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

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

【讨论】:

  • 请注意,C++11 也有可变参数宏 :)
  • @chris 确实如此,但在 C++ 中,您可能可以使用模板做得更好。
  • 可能在下面,但不是为了传递文件和行:(
  • @chris 你在说什么?您可以在模板中传递const char * 就好了。
  • 它允许您制作更复杂的printf,例如格式字符串和相关参数。
【解决方案3】:

注意:此解决方案仅适用于 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 更像一个整数类型(如果这很重要)。

【讨论】:

  • 不幸的是,你和我一样,误读了这个问题。他正在寻找他正在调用的函数的名称,而不是他当前所在的函数的名称。
  • @RichardJ.RossIII:不,我明白了。仔细阅读我的回答。
  • 你缺少的是__func__ 对他在这里尝试做的事情毫无用处。
  • @RichardJ.RossIII:__func__ 由导致错误的被调用函数使用。它被返回给调用者,因此调用者知道哪个函数导致了错误。
  • 但是我不能修改导致错误的函数,因为它是 API 的一部分。
【解决方案4】:

更新的解决方案

如果您没有定义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,它必须是一个整数类型)。然而,公平地说,原始代码 sn-p 也是如此。可以修改宏以接受变量作为参数(它不必总是命名为 errorCode,但仍必须在宏之外定义)。
  • 它打印用于调用函数的确切代码段。在某些(甚至大多数)情况下,这实际上可能是一件好事,但是我认为它在函数指针场景中并不理想(当然,在调用 API 时可能不常见)。此外,更难打印传递参数的实际值。这两种方法都可以在一定程度上解决,但代价是让它更笨拙和/或更不便携。

【讨论】:

  • 我显然错过了那里的逗号运算符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-04
  • 1970-01-01
  • 2011-06-01
  • 2011-05-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多