Linux 这可以自定义 printf 吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9260170/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is this possible to customize printf?
提问by Coren
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_argfamily to be able to prepend or apprend some text, but I feel like I would be re-implementing the wheel.
这个功能很方便,但也很有限。如果不制作新的包装器,我就无法准备或附加一些文本。我知道我可以使用va_arg系列来预先或附加一些文本,但我觉得我会重新实现轮子。
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 :
我想知道是否可以为 printf 编写自定义函数。我希望能够写出这样的东西:
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.
注意:我在 Ubuntu Linux 10.04 下使用 gcc。
采纳答案by Basile Starynkevitch
Sorry, but some answers are incorrect on Linux with Glibc
抱歉,在使用 Glibc 的 Linux 上有些答案是不正确的
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 your printf
format strings.
在Linux上使用GNU的glibc,你可以自定义的printf:你会打电话
register_printf_function
到如定义的含义%Y
在printf
格式字符串。
However, this behavior is Glibc specific, and might even become obsolete... I'm not sure I would recommend this approach!
然而,这种行为是 Glibc 特有的,甚至可能会过时......我不确定我会推荐这种方法!
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.
如果用 C++ 编码,C++ 流库有你可以扩展的操纵器,你也可以重载你的类型operator <<
等。
added in february 2018
2018 年 2 月添加
You could consider writing a GCC pluginhelping 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 of format
attribute (perhaps in gcc/tree.c
)
你可以考虑编写一个GCC 插件来帮助它(并改进一些扩展的类型检查printf
)。这并不容易(可能需要几周或几个月的工作时间),而且它是特定于 GCC 版本的(与 GCC 7 和 GCC 8 的插件代码不同)。您可以添加一些特定的内容#pragma
来通知您的插件有关额外的控制字符串说明符,例如您的%Y
和预期的类型。你的插件应该改变format
属性的处理(可能在gcc/tree.c
)
回答by David Heffernan
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.
这在标准 C 中是不可能的。您不能扩展printf
以添加自定义格式字符串。您的辅助函数方法可能与您在 C 的约束范围内获得的一样好。
回答by ThiefMaster
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.
可能最简单的解决方案是采用一个小的printf
实现(例如,来自嵌入式系统的 libc)并扩展它以适合您的目的。
回答by Ambroz Bizjak
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 like printf()
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.
不,这是不可能的。另一种方法是在自己周围制作自己的包装器printf()
。它会解析格式字符串并像printf()
这样处理转换。如果转换是您的自定义转换之一,它会打印您需要的任何内容,如果不是,它会调用系统的其中一个*printf()
函数来为您执行转换。
Note that this is a non-trivial task, and you have to be careful to parse the format string exactly like printf()
does. See man 3 printf
. You can read the variable argument list using functions in <stdarg.h>
.
请注意,这是一项非常重要的任务,您必须像解析格式字符串一样小心谨慎printf()
。见man 3 printf
。您可以使用 中的函数读取变量参数列表<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).
一旦有了这样的包装器,就可以通过使用函数指针(自定义转换不必硬编码到包装器中)使其可扩展。
回答by Till Hoffmann
You can use the sprintf
function to obtain a string representation of your struct:
您可以使用该sprintf
函数获取结构的字符串表示形式:
char* repr_mystruct(char* buffer, struct* my_struct)
{
sprintf(buffer, "[string:%s value1:%d value2:%d]", struct->value1, struct->value2);
return buffer;
}
and subsequently print the data to your output stream
然后将数据打印到您的输出流
char buffer[512]; //However large you need it to be
printf("My struct is: %s", repr_mystruct(buffer, &my_struct))
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.
注 2:格式字符串需要三个参数,但在示例中只传递了两个参数。
回答by Arthur Kushman
Just leave it here:
把它留在这里:
printf("%s: pid = %lu, ppid = %lu, pgrp = %lu, tpgrp = %lu\n", name,
(unsigned long int)getpid(), (unsigned long int)getppid(), (unsigned long int)getpgrp(),
(unsigned long int)tcgetpgrp(STDIN_FILENO));
回答by Arran Cudbard-Bell
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.
假设你想要可移植的代码,glibc 扩展就出来了。但即使遵守 C99 和 POSIX 标准也很有可能,我只写了一个。
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.
您不必重新实现 printf,不幸的是,您确实需要使代码足够智能以解析 printf 格式字符串,并从中推断出可变参数的 C 类型。
When variadic arguments are placed on the stack, no type or sizing information is included.
将可变参数放在堆栈上时,不包含任何类型或大小信息。
void my_variadic_func(fmt, ...)
{
}
my_variadic_func("%i %s %i", 1, "2", 3);
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.
在 64 位系统上的上述示例中,如果使用 48 位寻址,编译器可能最终会分配 4 字节 + 6 字节 + 4 字节 = 14 字节的堆栈内存,并将值打包到其中。我说可能,因为内存分配方式和参数打包方式是特定于实现的。
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 type int
, so you can advance your va_list cursor to the right point.
这意味着,为了访问%s
上述字符串中的指针值,您需要知道第一个参数的类型为int
,因此您可以将 va_list 光标移动到正确的位置。
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
).
获取该类型信息的唯一方法是查看格式字符串,并查看用户指定的类型(在本例中为%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.
因此,为了实现@AmbrozBizjak 的建议,将 subfmt 字符串传递给 printf,您需要解析 fmt 字符串,并在每个完整的非自定义 fmt 说明符之后,将 va_list 推进(无论多少字节宽)fmt 类型是。
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.
当您点击自定义 fmt 说明符时,您的 va_list 位于解压参数的正确位置。然后,您可以使用va_arg()
获取您的自定义参数(传递正确的类型),并使用它来运行您需要的任何代码,以生成您的自定义 fmt 说明符的输出。
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.
您将先前 printf 调用的输出与自定义 fmt 说明符的输出连接起来,并继续处理,直到到达末尾,此时您再次调用 printf 以处理格式字符串的其余部分。
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.
我的代码也使用了talloc...但是你可以使用标准的内存函数来完成它,只需要更多的字符串整理。
char *custom_vasprintf(TALLOC_CTX *ctx, char const *fmt, va_list ap)
{
char const *p = fmt, *end = p + strlen(fmt), *fmt_p = p, *fmt_q = p;
char *out = NULL, *out_tmp;
va_list ap_p, ap_q;
out = talloc_strdup(ctx, "");
va_copy(ap_p, ap);
va_copy(ap_q, ap_p);
do {
char *q;
char *custom;
char len[2] = { '##代码##', '##代码##' };
long width = 0, group = 0, precision = 0, tmp;
if ((*p != '%') || (*++p == '%')) {
fmt_q = p + 1;
continue; /* literal char */
}
/*
* Check for parameter field
*/
tmp = strtoul(p, &q, 10);
if ((q != p) && (*q == '$')) {
group = tmp;
p = q + 1;
}
/*
* Check for flags
*/
do {
switch (*p) {
case '-':
continue;
case '+':
continue;
case ' ':
continue;
case '0':
continue;
case '#':
continue;
default:
goto done_flags;
}
} while (++p < end);
done_flags:
/*
* Check for width field
*/
if (*p == '*') {
width = va_arg(ap_q, int);
p++;
} else {
width = strtoul(p, &q, 10);
p = q;
}
/*
* Check for precision field
*/
if (*p == '.') {
p++;
precision = strtoul(p, &q, 10);
p = q;
}
/*
* Length modifiers
*/
switch (*p) {
case 'h':
case 'l':
len[0] = *p++;
if ((*p == 'h') || (*p == 'l')) len[1] = *p++;
break;
case 'L':
case 'z':
case 'j':
case 't':
len[0] = *p++;
break;
}
/*
* Types
*/
switch (*p) {
case 'i': /* int */
case 'd': /* int */
case 'u': /* unsigned int */
case 'x': /* unsigned int */
case 'X': /* unsigned int */
case 'o': /* unsigned int */
switch (len[0]) {
case 'h':
if (len[1] == 'h') { /* char (promoted to int) */
(void) va_arg(ap_q, int);
} else {
(void) va_arg(ap_q, int); /* short (promoted to int) */
}
break;
case 'L':
if ((*p == 'i') || (*p == 'd')) {
if (len [1] == 'L') {
(void) va_arg(ap_q, long); /* long */
} else {
(void) va_arg(ap_q, long long); /* long long */
}
} else {
if (len [1] == 'L') {
(void) va_arg(ap_q, unsigned long); /* unsigned long */
} else {
(void) va_arg(ap_q, unsigned long long);/* unsigned long long */
}
}
break;
case 'z':
(void) va_arg(ap_q, size_t); /* size_t */
break;
case 'j':
(void) va_arg(ap_q, intmax_t); /* intmax_t */
break;
case 't':
(void) va_arg(ap_q, ptrdiff_t); /* ptrdiff_t */
break;
case '##代码##': /* no length modifier */
if ((*p == 'i') || (*p == 'd')) {
(void) va_arg(ap_q, int); /* int */
} else {
(void) va_arg(ap_q, unsigned int); /* unsigned int */
}
}
break;
case 'f': /* double */
case 'F': /* double */
case 'e': /* double */
case 'E': /* double */
case 'g': /* double */
case 'G': /* double */
case 'a': /* double */
case 'A': /* double */
switch (len[0]) {
case 'L':
(void) va_arg(ap_q, long double); /* long double */
break;
case 'l': /* does nothing */
default: /* no length modifier */
(void) va_arg(ap_q, double); /* double */
}
break;
case 's':
(void) va_arg(ap_q, char *); /* char * */
break;
case 'c':
(void) va_arg(ap_q, int); /* char (promoted to int) */
break;
case 'p':
(void) va_arg(ap_q, void *); /* void * */
break;
case 'n':
(void) va_arg(ap_q, int *); /* int * */
break;
/*
* Custom types
*/
case 'v':
{
value_box_t const *value = va_arg(ap_q, value_box_t const *);
/*
* Allocations that are not part of the output
* string need to occur in the NULL ctx so we don't fragment
* any pool associated with it.
*/
custom = value_box_asprint(NULL, value->type, value->datum.enumv, value, '"');
if (!custom) {
talloc_free(out);
return NULL;
}
do_splice:
/*
* Pass part of a format string to printf
*/
if (fmt_q != fmt_p) {
char *sub_fmt;
sub_fmt = talloc_strndup(NULL, fmt_p, fmt_q - fmt_p);
out_tmp = talloc_vasprintf_append_buffer(out, sub_fmt, ap_p);
talloc_free(sub_fmt);
if (!out_tmp) {
oom:
fr_strerror_printf("Out of memory");
talloc_free(out);
talloc_free(custom);
va_end(ap_p);
va_end(ap_q);
return NULL;
}
out = out_tmp;
out_tmp = talloc_strdup_append_buffer(out, custom);
TALLOC_FREE(custom);
if (!out_tmp) goto oom;
out = out_tmp;
va_end(ap_p); /* one time use only */
va_copy(ap_p, ap_q); /* already advanced to the next argument */
}
fmt_p = p + 1;
}
break;
case 'b':
{
uint8_t const *bin = va_arg(ap_q, uint8_t *);
/*
* Only automagically figure out the length
* if it's not specified.
*
* This allows %b to be used with stack buffers,
* so long as the length is specified in the format string.
*/
if (precision == 0) precision = talloc_array_length(bin);
custom = talloc_array(NULL, char, (precision * 2) + 1);
if (!custom) goto oom;
fr_bin2hex(custom, bin, precision);
goto do_splice;
}
default:
break;
}
fmt_q = p + 1;
} while (++p < end);
/*
* Print out the rest of the format string.
*/
if (*fmt_p) {
out_tmp = talloc_vasprintf_append_buffer(out, fmt_p, ap_p);
if (!out_tmp) goto oom;
out = out_tmp;
}
va_end(ap_p);
va_end(ap_q);
return out;
}
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.
可能值得做 Linux 人员所做的事情并重载 %p 以创建新的格式说明符,即 %pA %pB。这意味着静态 printf 格式检查不会抱怨。