【问题标题】:Is this possible to customize printf?这可以自定义printf吗?
【发布时间】:2012-03-04 20:01:15
【问题描述】:

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

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。

【问题讨论】:

    标签: c linux customization glibc


    【解决方案1】:

    抱歉,在使用 Glibc 的 Linux 上有些答案不正确

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

    但是,这种行为是 Glibc 特有的,甚至可能会过时......我不确定我是否会推荐这种方法!

    如果使用 C++ 编码,C++ 流库具有可以扩展的操纵器,您还可以为您的类型重载 operator << 等。

    2018 年 2 月添加

    您可以考虑编写一个GCC plugin 来帮助解决这个问题(并改进一些扩展printf 的类型检查)。这并不容易(可能需要几周或几个月的工作),而且它会是特定于 GCC 版本的(GCC 7 和 GCC 8 的插件代码不同)。您可以添加一些特定的 #pragma 来通知您的插件有关额外的控制字符串说明符,例如您的 %Y 以及它们预期的类型。你的插件应该改变format属性的处理方式(可能在gcc/tree.c中)

    【讨论】:

    • 您能详细说明为什么不推荐这种方法吗? C中有更好的方法吗?
    • 是的,这很有趣。但是为了一个函数,你必须重新编译所有 glibc。
    • 不,你不需要编译 Glibc。您只需从使用 Glibc 的程序(即 C 或 C++ 中的任何 Linux 程序)中调用 register_printf_function。但是,这不适用于非 Glibc 系统(例如 MacOSX 或 FreeBSD)。它实际上主要在 Linux 上运行。
    • 我找到了一个示例实现here
    • 如您所料,register_printf_function() 已被弃用。但是,添加了一个更新的功能:register_printf_specifier()。它的用法与旧的非常相似。我写了一个例子来注册 %b 说明符以打印二进制的无符号整数类型,你可以在 CodeReview 上看到:c - Register “%b” conversion specifier
    【解决方案2】:

    这在标准 C 中是不可能的。您不能扩展 printf 来添加自定义格式字符串。您的辅助函数方法可能与您在 C 的约束下获得的一样好。

    【讨论】:

      【解决方案3】:

      不,这是不可能的。另一种方法是围绕printf() 自己制作包装器。它将解析格式字符串并像printf() 那样处理转换。如果转换是您的自定义转换之一,它将打印您需要的任何内容,如果不是,它将调用系统的 *printf() 函数之一,让它为您执行转换。

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

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

      【讨论】:

      • 使用 Linux 和 Glibc,您可以自定义 printf。看我的回答!
      【解决方案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:格式字符串需要三个参数,但在示例中只传递了两个。

      【讨论】:

      • 是的,有问题。那是 UB,因为局部变量仅在其函数内部有效。您必须将缓冲区传入。
      • 这是错误的。函数返回时缓冲区超出范围,因此返回的指针无效。一种可能的解决方案是在文件范围(全局)上使用缓冲区变量。但是,当您需要在同一个 printf 中打印多个结构时,这是有问题的(参数评估之间没有序列点)。一个不错的解决方案是拥有 N 个全局缓冲区,并为 repr_mystruct() 提供一个参数,告诉它要使用 N 个缓冲区中的哪一个。然后在单个 printf() 中确保将不同的缓冲区索引传递给每个 repr_mystruct()。
      • @AmbrozBizjak 很好的解决方案是将缓冲区作为参数传入。
      • @DavidHeffernan 好吧,这取决于。传递一个整数肯定比传递一个缓冲区需要更少的代码(并且可能首先获得一个缓冲区)。
      • 修改了以上内容,允许显式传递缓冲区。
      【解决方案5】:

      很遗憾,这是不可能的。

      可能最简单的解决方案是采用小型 printf 实现(例如,来自嵌入式系统的 libc)并对其进行扩展以满足您的目的。

      【讨论】:

      • 使用 Linux 和 Glibc,您可以自定义 printf。看我的回答!
      • 我为此使用了snprintf implementation。使用snprintf,您需要内存中的格式化字符串空间。这对于许多用例来说已经足够了。您可以将格式化的字符串发送到您喜欢的任何文件(包括标准输出)。我想这与 Basile 的移植问题有关。
      【解决方案6】:

      把它留在这里:

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

      【讨论】:

      • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
      【解决方案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] = { '\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 格式检查不会报错。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多