可以自定义printf吗?
我有一些需要经常打印的结构。现在,我在这个结构周围使用经典的打印包装器:
void printf_mystruct(struct* my_struct)
{
if (my_struct==NULL) return;
printf("[value1:%d value2:%d]", struct->value1, struct->value2);
}
这个功能很方便,但也非常有限。如果不制作新的包装器,我无法预先添加或附加一些文本。我知道我可以使用 va_arg 系列来添加或添加一些文本,但我觉得我会重新实现轮子。
我想知道是否可以为 printf 编写一个自定义函数。我希望能够写出这样的东西:
register2printf("%mys", &printf_mystruct);
...
if (incorrect)
printf("[%l] Struct is incorrect : %mys\n", log_level, my_struct);
这可能吗?我该怎么做?
注意:我在 Ubuntu Linux 10.04 下,使用 gcc。
I have some struct that I need to print frequently. For now, I am using a classical print wrapper around this struct :
void printf_mystruct(struct* my_struct)
{
if (my_struct==NULL) return;
printf("[value1:%d value2:%d]", struct->value1, struct->value2);
}
This function is handy, but is also really limited. I cannot prepen or append some text without making a new wrapper. I know that I can use va_arg family to be able to prepend or apprend some text, but I feel like I would be re-implementing the wheel.
I am wondering if it's possible to write a customizing function to printf. I would like to be able to write something like this :
register2printf("%mys", &printf_mystruct);
...
if (incorrect)
printf("[%l] Struct is incorrect : %mys\n", log_level, my_struct);
Is this possible ? How can I do this ?
NB: I am under Ubuntu Linux 10.04 and I use gcc.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
抱歉,一些答案在使用 Glibc 的 Linux 上不正确
在使用 GNU Glibc 的 Linux 上,您可以自定义 printf:您可以调用
register_printf_function
例如定义printf
格式字符串中%Y
的含义。然而,这种行为是 Glibc 特有的,甚至可能变得过时......我不确定我是否会推荐这种方法!
如果使用 C++ 进行编码,C++ 流库具有您可以扩展的操纵器,并且您还可以为您的类型重载
2018 年 2 月添加的
operator <<
等。您可以考虑编写一个 GCC 插件 有助于这一点(并改进一些扩展的类型检查) printf)。这并不容易(可能需要几周或几个月的工作),而且它将是特定于 GCC 版本的(GCC 7 和 GCC 8 的插件代码不一样)。您可以添加一些特定的#pragma
来通知您的插件有关额外的控制字符串说明符,例如您的%Y
以及它们所需的类型。您的插件应该更改format
属性的处理(可能在gcc/tree.c
中)Sorry, but some answers are incorrect on Linux with Glibc
On Linux with a GNU Glibc, you can customize printf: you would call
register_printf_function
to e.g. define the meaning of%Y
in yourprintf
format strings.However, this behavior is Glibc specific, and might even become obsolete... I'm not sure I would recommend this approach!
If coding in C++, the C++ stream library has manipulators which you could extend, and you can also overload for your types the
operator <<
etc.added in february 2018
You could consider writing a GCC plugin helping that (and improving the typechecking of some extended
printf
). It won't be easy (probably a few weeks or months of work), and it would be GCC version specific (not the same plugin code for GCC 7 and GCC 8). you might add some specific#pragma
to inform your plugin about extra control string specifiers like your%Y
and the type expected for them. Your plugin should change the handling offormat
attribute (perhaps ingcc/tree.c
)这在标准 C 中是不可能的。您无法扩展
printf
来添加自定义格式字符串。您的辅助函数方法可能与您在 C 的限制内得到的一样好。This is not possible in standard C. You cannot extend
printf
to add custom format strings. Your helper function approach is probably about as good as you will get within the constraints of C.不,这是不可能的。另一种方法是围绕
printf()
本身制作您自己的包装器。它会像printf()
一样解析格式字符串并处理转换。如果转换是您的自定义转换之一,它将打印您需要的任何内容,如果不是,它将调用系统的*printf()
函数之一来让它为您执行转换。请注意,这是一项非常重要的任务,您必须像
printf()
一样小心地解析格式字符串。请参阅man 3 printf
。您可以使用
中的函数读取变量参数列表。一旦有了这样的包装器,您就可以通过使用函数指针使其可扩展(自定义转换不必硬编码到包装器中)。
No, this is not possible. An alternative is to make your own wrapper around
printf()
itself. It would parse the format string and process conversions likeprintf()
does. If a conversion is one of your custom conversions, it would print whatever you need, and if not, it would call one of the system's*printf()
functions to have it perform the conversion for you.Note that this is a non-trivial task, and you have to be careful to parse the format string exactly like
printf()
does. Seeman 3 printf
. You can read the variable argument list using functions in<stdarg.h>
.Once you have such a wrapper, you can make it extensible by employing function pointers (the custom conversions don't have to be hard-coded into the wrapper).
您可以使用
sprintf
函数来获取结构的字符串表示形式:然后将数据打印到输出流
编辑:修改函数以允许传递缓冲区(请参阅下面的讨论)。
注 2:格式字符串需要三个参数,但在示例中只传递了两个。
You can use the
sprintf
function to obtain a string representation of your struct:and subsequently print the data to your output stream
Edit: Modified the function to allow the passing of a buffer (see discussion below).
Note 2: The format string requires three arguments but in the example only two are passed.
不幸的是,这是不可能的。
也许最简单的解决方案是采用一个小的
printf
实现(例如,来自嵌入式系统的 libc)并对其进行扩展以满足您的目的。Unfortunately that's not possible.
Probably the easiest solution would be taking a small
printf
implementation (e.g. from a libc for embedded systems) and extending it to fit your purposes.把它留在这里:
Just leave it here:
假设您想要可移植的代码,glibc 扩展就已经出来了。但即使遵守 C99 和 POSIX 标准也是很有可能的,我刚刚写了一个。
您不必重新实现 printf,不幸的是,您确实需要使代码足够智能来解析 printf 格式字符串,并从中推断出可变参数的 C 类型。
当可变参数放置在堆栈上时,不包含类型或大小信息。
在上面 64 位系统上的示例中,使用 48 位寻址,编译器可能最终会分配 4 字节 + 6 字节 + 4 字节 = 14 字节的堆栈内存,并将值打包到其中。我说可能是因为内存的分配方式和参数的打包方式是特定于实现的。
这意味着,为了访问上述字符串中
%s
的指针值,您需要知道第一个参数的类型为int
,这样您就可以提前va_list 光标移至右侧点。获取该类型信息的唯一方法是查看格式字符串,并查看用户指定的类型(在本例中为
%i
)。因此,为了实现 @AmbrozBizjak 的建议,将 subfmt 字符串传递给 printf,您需要解析 fmt 字符串,并在每个完整的非自定义 fmt 说明符之后,将 va_list 推进 fmt 类型(无论多少字节宽)。
当您点击自定义 fmt 说明符时,您的 va_list 位于解压参数的正确位置。然后,您可以使用 va_arg() 获取自定义参数(传递正确的类型),并使用它来运行您需要的任何代码,以生成自定义 fmt 说明符的输出。
您可以连接先前 printf 调用的输出和自定义 fmt 说明符的输出,并继续处理,直到到达末尾,此时您再次调用 printf 来处理格式字符串的其余部分。
该代码更复杂(所以我将其包含在下面),但这使您对必须执行的操作有一个基本的了解。
我的代码也使用talloc...但是您可以使用标准内存函数来完成它,只需要更多的字符串处理。
编辑:
可能值得像 Linux 人员所做的那样并重载 %p 来创建新的格式说明符,即 %pA %pB。这意味着静态 printf 格式检查不会抱怨。
Assuming you want portable code, the glibc extensions are out. But even keeping to C99 and POSIX standards it is very much possible, I just wrote one.
You don't have to re-implement printf, you do unfortunately need to make your code smart enough to parse printf format strings, and infer the variadic argument's C types from them.
When variadic arguments are placed on the stack, no type or sizing information is included.
In the above example on a 64bit system, with 48bit addressing the compiler would likely end up allocating 4bytes + 6bytes + 4byte = 14bytes of stack memory, and packing the values into that. I say likely, because how the memory is allocated and the arguments packed is implementation specific.
That means, in order to access the pointer value for
%s
in the above string, you need to know that the first argument was of typeint
, so you can advance your va_list cursor to the right point.The only way you can get that type information is by looking at the format string, and seeing what type the user specified (in this case
%i
).So in order to implement @AmbrozBizjak's suggestion, of passing subfmt strings to printf, you need to parse the fmt string, and after each complete, non-custom fmt specifier, advance a va_list by (however many bytes wide) the fmt type was.
When you hit a custom fmt specifier, your va_list is at the right point to unpack the argument. You can then use
va_arg()
to get your custom argument (passing the right type), and use it to run whatever code you need to, to produce your custom fmt specifier's output.You concatenate the output from your previous printf call, and your custom fmt specifier's output, and carry on processing, until you reach the end, at which point you call printf again to process the rest of your format string.
The code is more complex (so I included it below), but that gives you a basic idea of what you have to do.
My code also uses talloc... but you can do it with the standard memory functions, just requires a bit more string wrangling.
EDIT:
It's probably worth doing what the Linux folks do and overloading %p to make new format specifiers, i.e. %pA %pB. This means the static printf format checks don't complain.