可以自定义printf吗?

发布于 2025-01-05 15:48:38 字数 602 浏览 3 评论 0原文

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

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

玉环 2025-01-12 15:48:38

抱歉,一些答案在使用 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 your printf 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 of format attribute (perhaps in gcc/tree.c)

倾城月光淡如水﹏ 2025-01-12 15:48:38

这在标准 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.

慕巷 2025-01-12 15:48:38

不,这是不可能的。另一种方法是围绕 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 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.

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>.

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

过期以后 2025-01-12 15:48:38

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

You can use the sprintf function to obtain a string representation of your struct:

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.

归属感 2025-01-12 15:48:38

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

也许最简单的解决方案是采用一个小的 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.

难理解 2025-01-12 15:48:38

把它留在这里:

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

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));
怎言笑 2025-01-12 15:48:38

假设您想要可移植的代码,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] = { '\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 == '

编辑:

可能值得像 Linux 人员所做的那样并重载 %p 来创建新的格式说明符,即 %pA %pB。这意味着静态 printf 格式检查不会抱怨。

)) { 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 格式检查不会抱怨。

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.

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.

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.

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.

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 == '

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.

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文