这可以定制 printf 吗?
- 2024-11-11 08:27:00
- admin 原创
- 26
问题描述:
我有一些需要经常打印的结构。目前,我正在使用经典的打印包装器来包装这个结构:
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
", log_level, my_struct);
这可能吗?我该怎么做?
注意:我在 Ubuntu Linux 10.04 下并且使用 gcc。
解决方案 1:
抱歉,但在使用 Glibc 的 Linux 上,有些答案是不正确的
在带有 GNU Glibc 的 Linux 上,您可以自定义 printf:您可以调用
它来定义格式字符串中register_printf_function
的含义。%Y
`printf`
然而,这种行为是 Glibc 特有的,甚至可能会过时...我不确定我是否会推荐这种方法!
如果使用 C++ 编码,C++ 流库具有您可以扩展的操纵器,并且您还可以为您的类型重载operator <<
等等。
2018 年 2 月添加
您可以考虑编写一个GCC 插件来帮助实现这一点(并改进一些扩展的类型检查printf
)。这并不容易(可能需要几周或几个月的工作),并且它将是特定于 GCC 版本的(GCC 7 和 GCC 8 的插件代码不同)。您可以添加一些特定的内容#pragma
来通知您的插件有关额外的控制字符串说明符(例如您的)%Y
以及它们的预期类型。您的插件应该更改format
属性的处理(也许在gcc/tree.c
)
解决方案 2:
这在标准 C 中是不可能的。您无法扩展printf
以添加自定义格式字符串。您的辅助函数方法可能在 C 的限制内尽可能好。
解决方案 3:
不,这是不可能的。另一种方法是自己制作包装器printf()
。它会解析格式字符串并像printf()
上面那样处理转换。如果转换是您的自定义转换之一,它会打印您需要的任何内容,如果不是,它会调用系统*printf()
函数之一让它为您执行转换。
请注意,这不是一项简单的任务,您必须小心地像 一样解析格式字符串printf()
。请参阅man 3 printf
。您可以使用 中的函数读取变量参数列表<stdarg.h>
。
一旦您有了这样的包装器,您就可以通过使用函数指针使其可扩展(自定义转换不必硬编码到包装器中)。
解决方案 4:
您可以使用该sprintf
函数来获取结构的字符串表示形式:
char* repr_mystruct(char* buffer, struct* my_struct)
{
sprintf(buffer, "[string:%s value1:%d value2:%d]", struct->value1, struct->value2);
return buffer;
}
然后将数据打印到输出流
char buffer[512]; //However large you need it to be
printf("My struct is: %s", repr_mystruct(buffer, &my_struct))
编辑:修改了该功能以允许传递缓冲区(见下面的讨论)。
注 2:格式字符串需要三个参数,但在示例中只传递了两个。
解决方案 5:
不幸的是这是不可能的。
可能最简单的解决方案是采取一个小的printf
实现(例如来自嵌入式系统的 libc)并扩展它以满足您的目的。
解决方案 6:
就留在这里吧:
printf("%s: pid = %lu, ppid = %lu, pgrp = %lu, tpgrp = %lu
", name,
(unsigned long int)getpid(), (unsigned long int)getppid(), (unsigned long int)getpgrp(),
(unsigned long int)tcgetpgrp(STDIN_FILENO));
解决方案 7:
假设您想要可移植的代码,glibc 扩展是不行的。但即使遵守 C99 和 POSIX 标准也是非常有可能的,我刚刚写了一个。
您不必重新实现 printf,但不幸的是,您需要让您的代码足够智能,可以解析 printf 格式字符串,并从中推断出可变参数的 C 类型。
当可变参数放在堆栈上时,不包含任何类型或大小信息。
void my_variadic_func(fmt, ...)
{
}
my_variadic_func("%i %s %i", 1, "2", 3);
在上面的 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...但是您可以使用标准内存函数来实现,只需要进行更多的字符串处理。
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;
}
编辑:
也许值得效仿 Linux 开发人员的做法,重载 %p 以创建新的格式说明符,即 %pA %pB。这意味着静态 printf 格式检查不会产生任何抱怨。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件