问题
我有一种方法可以通过将函数替换为宏来包装函数,以便记录调用和返回代码。以下是一个可行的示例:
int rc;
int foo(int a, int b);
int bar(int a, char *b, int *c);
void LogRet(char *fn, char *file, char *from, int ln, int ret)
{
printf("%s.%s.%d: %s() ret:%08x\n", file, from, ln, fn, ret);
}
#define foo(args, ...) (rc = (foo)(args, ##__VA_ARGS__), LogRet("foo", __FILE__, __FUNCTION__, __LINE__, rc), rc)
#define bar(args, ...) (rc = (bar)(args, ##__VA_ARGS__), LogRet("bar", __FILE__, __FUNCTION__, __LINE__, rc), rc)
宏替换函数的宏调用该函数,记录函数名称及其被调用的位置以及返回代码。在语法上,任何函数都可以使用同样的方式进行包装,只需要在宏中替换函数名即可。我想做的是创建一个包装宏,其中foo的重新定义宏将类似于:
#define foo(args, ...) WRAPPPER(foo)
我了解stringify和double stringify的基本概念,但是我甚至无法让WRAPPER宏执行真正的函数调用。理想情况下,我希望将其简化为单个WRAP(foo)语句。原因是,我有大约100个或更多需要包装的函数,并且希望通过一个简单的强制包含文件来实现简单的包装。在放弃这个想法之前,我得出结论这是不可能的,但我想在这里询问一下。我正在使用clang和vc++,但最好能够在任何编译器上使用,因为我要调试很多不同的系统。
Jonathan Leffler答案的适应性
由于我是新手,所以我不确定这是否应该是单独的答案还是更新编辑。这基本上是Jonathan Leffler的答案。这是一个功能示例。虽然它调用的两个函数毫无意义,但目标是拥有一个可以用任何参数列表包装任何函数的宏。我主要用于记录大型代码库中的使用流程,该库存在问题。此外,我的原始示例有一个明显的缺陷。通过在全局变量中存储返回代码,它不是线程安全的,除非通过繁琐的TLS准备。现在已经删除全局变量,宏不再使用序列运算符返回值,它被保留并由日志函数返回。此外,正如Augurar指出的,并在Jonathan的示例中显示的那样,如果在同一文件中声明宏和函数声明,则需要括号。
#include <stdio.h>
#include <string.h>
int foo(int a, int b);
int bar(int a, char *b, int *c);
#if defined (_DEBUG) || defined (DEBUG)
// Short version of __FILE__ without path requires runtime parsing
#define __SFILE__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#ifdef WIN32
#define WRAPPER(func, ...) LogRet(#func, __SFILE__, __FUNCTION__, __LINE__, (func)(__VA_ARGS__))
#else
#define WRAPPER(func, ...) LogRet(#func, __SFILE__, __func__, __LINE__, (func)(__VA_ARGS__))
#endif
inline int LogRet(const char *fn, const char *file, const char *from, int ln, int ret)
{
printf("%s.%s.%d: %s() ret:%08x\n", file, from, ln, fn, ret);
return ret;
}
#define foo(...) WRAPPER(foo, __VA_ARGS__)
#define bar(...) WRAPPER(bar, __VA_ARGS__)
#endif
int main(void)
{
int x = foo(1, 2);
bar(2, "doubled", &x);
return 0;
}
#ifdef foo
#undef foo
#undef bar
#endif
// If and only if the function definition is in the same file with the macros, you must either undefine the macros or
// parenthesize the function e.g. int (foo)(int a, int b) { ... }
int foo(int a, int b)
{
printf("%d + %d = %d\n", a, b, a + b);
return a + b;
}
int (bar)(int a, char *b, int *c)
{
printf("%d %s = %d\n", *c, b, a * *c);
return *c * a;
}
发布构建输出:
1 + 2 = 3
3 doubled = 6
调试构建输出:
1 + 2 = 3
test.cpp.main.35: foo() ret:00000003
3 doubled = 6
test.cpp.main.36: bar() ret:00000006
主要的好处是不需要在代码中查找每个foo()或bar()的出现,以插入调试打印来记录调用和结果,或者您想要插入的任何调试代码。
foo
宏似乎是递归定义的。此外,包括你尝试定义WRAPPER
的代码(即使不成功)会更有帮助。 - augurar__func__
(这次格式没错)。我认为我可以将rc声明为thread_local,但这样不太可移植,而且没有存储会更整洁。 - ChocoBilly