这是可以自定义的printf?

我有一些结构,我需要经常打印。 现在,我正在使用一个经典的打印包装这个结构:

void printf_mystruct(struct* my_struct) { if (my_struct==NULL) return; printf("[value1:%d value2:%d]", struct->value1, struct->value2); } 

这个function很方便,但也是非常有限的。 如果不制作新的包装,我不能预加或附加一些文本。 我知道,我可以使用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。

Solutions Collecting From Web of "这是可以自定义的printf?"

对不起,但有些答案在Glibc的Linux上是不正确的

在使用GNU Glibc的Linux上,您可以自定义printf :您可以调用register_printf_function来例如在printf格式字符串中定义%Y的含义。

但是,这种行为是特定的Glibc,甚至可能会过时…我不知道我会推荐这种方法!

如果使用C ++进行编码,C ++流库有可以扩展的操纵operator << ,也可以重载operator <<等等。

这在标准C中是不可能的。你不能扩展printf来添加自定义的格式字符串。 你的帮助函数的方法可能和你在C的约束条件下一样好

不幸的是,这是不可能的。

可能最简单的解决方案是采取一个小的printf实现(例如从一个嵌入式系统的libc),并将其扩展到适合您的目的。

不,这是不可能的。 另一种方法是自己打包printf() 。 它会解析格式字符串和像printf()这样的处理转换。 如果转换是您的自定义转换之一,则会打印您所需的任何内容,如果不是,则会调用系统的其中一个*printf()函数来为您执行转换。

请注意,这是一个不重要的任务,你必须小心地分析格式字符串就像printf()一样。 见man 3 printf 。 您可以使用<stdarg.h>函数来读取变量参数列表。

一旦你有了这样一个包装器,你可以通过使用函数指针来扩展它(自定义转换不必被硬编码到包装器中)。

您可以使用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:格式字符串需要三个参数,但在该示例中只传递了两个参数。

只要把它留在这里:

 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)); 

假设你想要可移植的代码,那glibc的扩展就没有了。 但是即使遵循C99和POSIX的标准,我也只写了一个。

您不必重新实现printf,但不幸的是,需要使代码足够聪明来解析printf格式字符串,并从中推断可变参数的C类型。

当可变参数放在堆栈上时,不包括类型或大小信息。

 void my_variadic_func(fmt, ...) { } my_variadic_func("%i %s %i", 1, "2", 3); 

在上面的64位系统的例子中,对于48位寻址,编译器可能最终分配4bytes + 6bytes + 4byte = 14bytes的堆栈内存,然后将值打包。 我说可能,因为如何分配内存和打包的参数是具体实现。

这意味着,为了在上面的字符串中访问%s的指针值,你需要知道第一个参数是int类型的,所以你可以将你的va_list光标提前到正确的点。

获取该类型信息的唯一方法是查看格式字符串,并查看用户指定的类型(在本例中为%i )。

因此,为了实现@ AmbrozBizjak的建议,将子字符串传递给printf,需要解析fmt字符串,并且在每个完整的非定制fmt说明符之后,通过fmt类型(不管多宽字节)推进一个va_list。

当你打一个自定义的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] = { '\0', '\0' }; 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 '\0': /* 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格式的检查不会抱怨。