【发布时间】:2014-03-14 03:48:24
【问题描述】:
问题
我有一种方法可以通过用宏替换函数来包装函数,这样我就可以记录调用和返回代码。这是一个有效的示例:
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)
它所替代的函数的宏调用该函数并记录函数名称、调用位置以及返回码。包装任何函数都使用相同的语法,只需要在宏中替换函数名 3 次。我想做的是创建包装宏,其中为 foo 重新定义的宏类似于:
#define foo(args, ...) WRAPPPER(foo)
我了解 stringify 和 double stringify 的基础知识,但我什至无法使用 WRAPPER 宏来执行真正的函数调用。理想情况下,我想把它归结为单个 WRAP(foo) 语句。原因是,我有大约 100 个或更多我想包装的函数,它想从一个简单的强制包含文件中简单地完成它。我得出的结论是这是不可能的,但我想在放弃这个想法之前我会在这里问。我正在使用 clang 和 vc++,如果这有什么不同的话,但是在我调试很多不同的系统时,在任何编译器上都有这个会很好。
乔纳森·莱弗勒的回答改编
由于我是新来的,我不确定这应该是单独的答案还是编辑更新。这基本上是乔纳森莱弗勒的回答。这是一个功能示例。虽然它调用的 2 个函数毫无意义,但目标是拥有一个可以用任何 arg 列表包装任何函数的宏。我的主要用途是在有问题的大型代码库中记录使用流程。此外,我的原始样本有一个明显的弱点。通过将返回代码存储在全局中,如果没有 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的尝试定义(即使不成功)也会有所帮助。 -
foo 宏将 foo() 函数调用替换为“调试”版本,该版本调用它正在替换的实际函数。这就是为什么宏中的 foo() 用作 (foo)() 这将防止它作为宏扩展并调用实际函数。宏然后打印带有函数名称的调试消息及其返回码,然后确保真实代码获得返回码。在宏中完成此操作的原因是,这意味着根本无需更改代码即可获得记录的调试运行。宏可以很容易地包含在调试版本中,也可以不包含。示例宏确实有效。
-
啊,我明白了。我想你在声明函数名时也必须加上括号。尴尬,但我想它有效。
-
无需为函数声明或代码加括号。如果定义了宏,预处理器就知道“foo”和“(foo)”之间的区别。一个常见的用途是在您无法轻松覆盖 lib 中的 malloc() 时捕获它。您将宏 malloc 定义为您的函数,然后当您的代码完成所需的任何调试跟踪时,使用 (malloc)(size); 调用 malloc这适用于我曾经使用过的每个编译器。括号明确告诉编译器不要使用宏。宏通常位于#if DEBUG 或#if MYDEBUG 中,其优点是无需更改代码。
-
@augurar 我说错了。如果宏是在与函数声明相同的源文件中定义的,那么您对函数声明的括号是正确的。由于我通常将这些用于我没有源代码的库,因此很少出现。我希望我没有混淆任何人。
标签: c++ macros variadic-functions