【问题标题】:Variadic Macro calling fprintf: how to add arguments to __VA_ARGS__?可变参数宏调用 fprintf:如何向 __VA_ARGS__ 添加参数?
【发布时间】:2018-11-28 15:13:05
【问题描述】:

我有两个宏:

 #define LogFunction(str)       fprintf(stdout, "%s: %s\n",__FUNCTION__,(str))
 #define LogPrintf(f_, ...)     fprintf(stdout, (f_), ##__VA_ARGS__)

所以我可以这样使用它们:

void MyFunction()
{
    int N=4;
    LogFunction("START");      // Outputs "MyFunction: START"
    LogPrintf("N=%d\n", N);    // Outputs "N=4"
}

我想改变的是

  1. 在 LogPrintf 的开头添加 FUNCTION,就像在 LogFunction 中一样
  2. 在LogPrintf末尾加“\n”,不用记得自己放

所以最后我的输出可以只有一个宏。

我试图了解Appending to __VA_ARGS__ 是否有用,但我承认我不明白它是否与我的情况有关:(

谢谢。

【问题讨论】:

  • LogPrintf("N=%d\n") 看起来像未定义的行为。 4 来自哪里?
  • LogPrintf 调用您自己的辅助函数,该函数又调用vprintf。该助手可以在之前和/或之后输出您想要的任何内容。
  • 我希望您没有使用stdout 进行实际日志记录。当您需要轮换日志文件或日志文件恰好填满您的文件系统时会发生什么?
  • 这不是 C++ 代码。

标签: c macros printf variadic-macros


【解决方案1】:

为什么不分三步做呢?

#define LogPrintf(f_, ...)   do { fprintf(stdout, "%s: ",__FUNCTION__); \
                                  fprintf(stdout, (f_), ##__VA_ARGS__); \
                                  fprintf(stdout,"\n"); } while(0)

这会打印 3 次,但至少它很简单并且可以满足您的需求。 do while(0) 技巧确保这是一个唯一的块(当使用不带括号的if 时)并且需要分号。

【讨论】:

  • 为什么不分 3 步完成? 一个原因可能是确保多线程进程没有来自不同线程的日志条目混杂在一起。但是如果进程是单线程的,那就不适用了。
  • 尽管我想在一个 printf 中做到这一点,但这听起来很困难。并且对 printf 的 3 次调用或对 printf 的一次调用不会改变线程方面的事情。除非您使用互斥锁,否则您已经可以使用带有 2 个并发 printfs 的混合行。
  • 并非如此,@Jean-FrançoisFabre。从不同线程中的单个 fprintf 调用到同一文件的输出不会混合。请参阅标准的paragraphs 7.21.2/7-8。但是不同线程中多个调用的输出可能会在每个调用的基础上交错。
  • @JohnBollinger 您提到的锁定要求是 C11(可能是更高版本)并且在 the C99 standard 中缺失。可能仍有许多运行系统具有未实现该锁的 C 运行时库。
【解决方案2】:

如果您愿意将 LogPrintf 的第一个参数作为字符串文字,那么您应该能够使用字符串连接来实现您的目标:

// Assumes f_ always corresponds to a string literal:
#define LogPrintf(f_, ...)    fprintf(stdout, "%s: " f_ "\n", __FUNCTION__, ##__VA_ARGS__)

但是请注意,在标准 C 中,LogPrintf 宏至少需要 两个 参数,而 ## 没有位置。我保留它只是因为您在原始代码中使用它。

但是,如果您必须接受字符串文字以外的格式字符串表达式,那么您最简单的替代方法是执行多个 I/O 调用,正如另一个答案所建议的那样:

#define LogPrintf(f_, ...)    do {         \
    fprintf(stdout, "%s: ", __FUNCTION__); \
    fprintf(stdout, (f_), ##__VA_ARGS__);  \
    fputc('\n', stdout);                   \
} while (0)

请注意,在这种情况下,宏扩展为 语句(没有尾随分号),而在另一种情况下,宏扩展为 表达式。如果您想要任何 I/O 函数的返回值,那么在这种情况下您必须为此做出特殊规定。

如果这对您也不起作用,那么最终的替代方法是编写和使用辅助函数,正如 cmets 中所建议的那样:

#define LogPrintf(f_, ...)    log_printf_impl(stdout, __FUNCTION__, (f_), ##__VA_ARGS__)

int log_printf_impl(FILE *f, const char *func, const char *fmt, ...) {
    static const char prefix[] = "%s: ";
    size_t flen = strlen(fmt);
    va_list args;
    int result = -1;
    char *aug_fmt = malloc(sizeof(prefix) + strlen(fmt) + 1);

    if (aug_fmt) {
        va_start(args, fmt);
        sprintf(aug_fmt, "%s%s\n", prefix, fmt);
        result = vfprintf(f, aug_fmt, func, args);
        va_end(args);
        free(aug_fmt);
    }

    return result;
}

【讨论】:

  • 关于标题问题,请特别注意,在__VA_ARGS__ 前面添加或附加参数很简单,前提是您依赖并遵守标准 C 的要求,即它始终至少对应一个实际的论点。你可以这样做,就像我的第一个替代节目一样。
  • 谢谢!您的第一个示例运行良好,您的答案的其他部分得到了很好的解释和有用的学习!
  • 关于##__VA_ARGS__ 中的##,如果我只使用一个参数,它是处理尾随“,”的 GCC 方式。当我选择原始宏时,我找不到问题,但这里也有解释:link
  • 我知道## 的用途,@Parduz。我只是指出您描述的行为是一个扩展,而不是标准C 的一部分,就像在没有至少一个可变参数的情况下调用可变参数宏一样。只有您可以决定在这方面您的代码不可移植是否对您很重要。
猜你喜欢
  • 1970-01-01
  • 2014-03-03
  • 2011-01-20
  • 1970-01-01
  • 2014-09-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-09
  • 2020-09-14
相关资源
最近更新 更多