如何将 printf() 包装到函数或宏中?
Posted
技术标签:
【中文标题】如何将 printf() 包装到函数或宏中?【英文标题】:How to wrap printf() into a function or macro? 【发布时间】:2014-01-05 12:55:31 【问题描述】:这听起来像是一个面试问题,但实际上是一个实际问题。
我正在使用嵌入式平台,并且只有这些功能的等价物:
printf() snprintf()此外,printf()
实现(和签名)可能会在不久的将来发生变化,因此对其的调用必须驻留在单独的模块中,以便以后轻松迁移。
鉴于此,我可以将日志调用包装在某个函数或宏中吗?目标是我的源代码在一千个地方调用THAT_MACRO("Number of bunnies: %d", numBunnies);
,但对上述函数的调用只在一个地方看到。
编译器:arm-gcc -std=c99
编辑:只是提一下,但在 2000 年的最佳实践之后,可能更早,内联函数比宏要好得多,原因有很多。
【问题讨论】:
你的编译器支持可变参数宏吗? 有哪些编译器限制?如果这必须在 C99 之前的 C 版本上运行,则很难作为宏移植。 @KerrekSB 我想为什么?这几天 cmets 被屏蔽了? 【参考方案1】:有两种方法可以做到这一点:
变量宏
#define my_printf(...) printf(__VA_ARGS__)
转发va_args
的函数
#include <stdarg.h>
#include <stdio.h>
void my_printf(const char *fmt, ...)
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
还有vsnprintf
、vfprintf
以及stdio
中你能想到的任何东西。
【讨论】:
此外,您可以找到一些关于宏(和可变参数宏)的文档here。 @Roddy 是的,如果您想转发所有参数。我不鼓励这样做,因为您不能以这种方式定义无操作宏。使用类似函数的宏,如果需要,您可以随时将其设为无操作。 我希望有人编辑了这个答案,这样我就可以删除我的赞成票。我没有 vprintf 或其他幻想。嵌入式,你知道的。 对不起语气。我真的不能为此使用标准库。这是一个基于 ARM 的定制平台。 我一直在尝试这样的各种方法,但是 1. 我无法制作通过命名空间工作的宏;#define Log::WriteLine(_Format, ...) printf(_Format, __VA_ARGS__)
不起作用。并且 2. 它没有以石灰绿色显示 %s
等,或者验证输入......因此作为替代品是无用的。如果有任何方法可以获得自定义 printf,以石灰绿色显示 %s
等,并进行必要的验证?【参考方案2】:
由于您可以使用 C99,我会将其包装在 variadic macro 中:
#define TM_PRINTF(f_, ...) printf((f_), __VA_ARGS__)
#define TM_SNPRINTF(s_, sz_, f_, ...) snprintf((s_), (sz_), (f_), __VA_ARGS__)
因为你没有说你有vprintf
或类似的东西。如果你确实有类似的东西,你可以将它包装在一个函数中,就像 Sergey L 在他的回答中提供的那样。
上述 TM_PRINTF 不适用于空的 VA_ARGS 列表。 至少在 GCC 中可以这样写:
#define TM_PRINTF(f_, ...) printf((f_), ##__VA_ARGS__)
如果__VA_ARGS__
为空,则两个## 符号会删除它们前面多余的逗号。
【讨论】:
TM_PRINTF("Starting ei-oh!");
产生error: expected expression before ')' token
。如果没有格式字符串参数,它就可以工作。可变参数的数量是否需要非零?
看来上面的错误没有gcc扩展是无法修复的:gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
您可以解决删除 _f 部分并仅保留 varidac 的问题。对我来说工作没有问题,但我不知道它是否超出标准
@lesto,是的,它“有效”,但删除 f_
将使 TM_PRINTF()
允许。如果该字符串在代码中,编译器稍后会抱怨 printf()
与它的函数签名不匹配。在这种情况下,最好让编译器抱怨TM_PRINTF
,因为这样更明显。
@ldav1s 我不明白你所说的“可变参数”宏是什么意思。那些省略号 ...
是如何神奇地将它们转发到 __VA_ARGS__
的?【参考方案3】:
如果您可以忍受将调用包含在 两个 括号中,您可以这样做:
#define THAT_MACRO(pargs) printf pargs
然后使用它:
THAT_MACRO(("This is a string: %s\n", "foo"));
^
|
OMG
这是可行的,因为从预处理器的角度来看,整个参数列表变成一个宏参数,用括号代替。
这比单纯的做要好
#define THAT_MACRO printf
因为它允许你定义它:
#define THAT_MACRO(pargs) /* nothing */
这会“吃掉”宏参数,它们永远不会成为编译代码的一部分。
更新当然,在 C99 中这种技术已经过时了,只要使用可变参数宏就可以了。
【讨论】:
天啊,感谢您的出色回答。我想在接下来的几年里,你会得到所有可怜的 C89 程序员的支持 :) 这种技术的一个重要副作用:所有对THAT_MACRO
的调用都需要双括号,即使是单参数调用,例如THAT_MACRO(("Foo Bar"))
。 --怀着爱,几年后一个可怜的C89程序员。【参考方案4】:
#define TM_PRINTF(f_, ...) printf((f_), ##__VA_ARGS__)
##
令牌将启用 TM_PRINTF("aaa");
【讨论】:
这就像魔术!每个案例都为我节省了很多重复的宏! ## 是如何工作的? 是的,这有效,而接受的答案却没有。谢谢! 请注意,这是一个编译器扩展,而不是 C99 标准。基本上,编译器编写者,他们是聪明人,认识到标准中的这种疏忽,并找到了解决方法。需要注意的是,它不能保证在每个符合 C99 的编译器上都能工作,并且一些编译器可能对同一件事使用不同的语法。【参考方案5】:#define PRINTF(...) printf(__VA_ARGS__)
这样工作:
它定义参数化宏 PRINTF 以接受(最多)无限参数,然后将其从 PRINTF(...)
预处理到 printf(__VA_ARGS__)
。 __VA_ARGS__
在参数化宏定义中用于表示给定的参数(因为你不能命名无限参数,对吗?)。
【讨论】:
【参考方案6】:图书馆有限?嵌入式系统?需要尽可能多的性能?没问题!
正如this question 的答案所示,您可以使用汇编语言将不接受 VA_LIST 的函数包装成可以接受的函数,以很少的成本实现您自己的 vprintf!
虽然这会起作用,并且几乎可以肯定会产生您想要的性能和抽象,但我还是建议您获得一个功能更丰富的标准库,也许可以通过对uClibc 的部分进行切片。除非您绝对需要每个周期,否则这样的解决方案肯定是比使用汇编更便携且整体更有用的答案。
毕竟,这就是存在此类项目的原因。
【讨论】:
@ΈρικΚωνσταντόπουλος 是的,这是极少数的系统之一,ASM 可以接受程序员时间的折衷。【参考方案7】:这是@ldav1 出色答案的略微修改版本,它在日志之前打印时间:
#define TM_PRINTF(f_, ...) \
\
struct tm _tm123_; \
struct timeval _xxtv123_; \
gettimeofday(&_xxtv123_, NULL); \
localtime_r(&_xxtv123_.tv_sec, &_tm123_); \
printf("%2d:%2d:%2d.%d\t", _tm123_.tm_hour, _tm123_.tm_min, _tm123_.tm_sec, _xxtv123_.tv_usec); \
printf((f_), ##__VA_ARGS__); \
;
【讨论】:
以上是关于如何将 printf() 包装到函数或宏中?的主要内容,如果未能解决你的问题,请参考以下文章