【问题标题】:C++ Wrapping a wrapper macro with variable arguments?C ++用可变参数包装包装宏?
【发布时间】: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


【解决方案1】:

此代码看起来符合您的要求:

#include <stdio.h>

int rc;
int foo(int a, int b);
int bar(int a, char *b, int *c);

extern void LogRet(const char *fn, const char *file, const char *from, int ln, int ret);

void 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);
}

#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)

extern void caller1(void);

void caller1(void)
{
    int d;
    int e = foo(1, 2);
    int f = bar(3, "abc", &d);
    printf("%s(): %d + %d + %d = %d\n", __func__, d, e, f, d + e + f);
}

#undef foo
#undef bar

#define WRAPPER(func, ...) ((rc = (func)(__VA_ARGS__)), LogRet(#func, __FILE__, __func__, __LINE__, rc), rc)

#define foo(...) WRAPPER(foo, __VA_ARGS__)
#define bar(...) WRAPPER(bar, __VA_ARGS__)

extern void caller2(void);

void caller2(void)
{
    int d;
    int e = foo(2, 3);
    int f = bar(3, "abc", &d);
    printf("%s(): %d + %d + %d = %d\n", __func__, d, e, f, d + e + f);
}

int (foo)(int a, int b)
{
    return (a + b) % 3;
}

int (bar)(int a, char *b, int *c)
{
    int d = b[a];
    *c = d;
    return a + d;
}

int main(void)
{
    caller1();
    caller2();
    return 0;
}

示例输出:

wrapper.c.caller1.23: foo()  ret:00000000
wrapper.c.caller1.24: bar()  ret:00000003
caller1(): 0 + 0 + 3 = 3
wrapper.c.caller2.41: foo()  ret:00000002
wrapper.c.caller2.42: bar()  ret:00000003
caller2(): 0 + 2 + 3 = 5

预处理的源代码(不包括#include &lt;stdio.h&gt;的输出):

# 2 "wrapper.c" 2

int rc;
int foo(int a, int b);
int bar(int a, char *b, int *c);

extern void LogRet(const char *fn, const char *file, const char *from, int ln, int ret);

void 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);
}





extern void caller1(void);

void caller1(void)
{
    int d;
    int e = (rc = (foo)(1, 2), LogRet("foo", "wrapper.c", __FUNCTION__, 23, rc), rc);
    int f = (rc = (bar)(3, "abc", &d), LogRet("bar", "wrapper.c", __FUNCTION__, 24, rc), rc);
    printf("%s(): %d + %d + %d = %d\n", __func__, d, e, f, d + e + f);
}
# 36 "wrapper.c"
extern void caller2(void);

void caller2(void)
{
    int d;
    int e = ((rc = (foo)(2, 3)), LogRet("foo", "wrapper.c", __func__, 41, rc), rc);
    int f = ((rc = (bar)(3, "abc", &d)), LogRet("bar", "wrapper.c", __func__, 42, rc), rc);
    printf("%s(): %d + %d + %d = %d\n", __func__, d, e, f, d + e + f);
}

int (foo)(int a, int b)
{
    return (a + b) % 3;
}

int (bar)(int a, char *b, int *c)
{
    int d = b[a];
    *c = d;
    return a + d;
}

int main(void)
{
    caller1();
    caller2();
    return 0;
}

在 Mac OS X 10.9.2 上使用 GCC 4.8.2 测试。

【讨论】:

  • 就是这样,谢谢!我混淆了将 var args 传递给包装器。唯一需要的是#ifdef WIN32,因为MS VC 使用该程序较晚,并且无法识别_ func _ 只有_ FUNCTION 。不在 VS 2008、VS2010 和 VS 2012 上。我没有尝试过 VS 2013。我没有研究过 func _ 因为我不能在任何地方都使用它并且不确定益处。我知道 _ FILE _ 像 C 字符串一样工作。我使用我自己的宏 _ SFILE 去除 _ _FILE _ 的路径并只打印文件名,但不想用它来混淆示例。再次感谢,这将得到很多重用。
  • 对该评论的格式表示歉意。不知道如何解释双下划线然后编辑超时。
  • 评论格式很有趣,下划线和星号都会触发斜体,或粗体,或粗斜体。使用反引号保护大多数包含__ 的单词。 __func____FUNCTION__ 之间的选择完全取决于您; __func__ 是 C99,但 GCC 很高兴地支持 __FUNCTION__。其他编译器可能对什么是正确的有不同的看法。 C 标准说 __func__,但是 YMMV。
猜你喜欢
  • 2022-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-27
  • 2020-07-31
相关资源
最近更新 更多