这可以定制 printf 吗?

2024-11-11 08:27:00
admin
原创
27
摘要:问题描述:我有一些需要经常打印的结构。目前,我正在使用经典的打印包装器来包装这个结构:void printf_mystruct(struct* my_struct) { if (my_struct==NULL) return; printf("[value1:%d value2:%d]&...

问题描述:

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

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 格式检查不会产生任何抱怨。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用