【问题标题】:How is vprintf implemented? [duplicate]vprintf 是如何实现的? [复制]
【发布时间】:2014-11-13 21:09:11
【问题描述】:

如果想用 C 语言编写一个将变量参数列表传递给printf 的函数,则必须使用vprintf 版本。如何为自定义函数实现这种机制?

换句话说,设置vprintfprintf 的本质是如何在符合标准的C 中实现的?

【问题讨论】:

标签: c variadic-functions


【解决方案1】:

如果您想编写一个以va_list 作为参数的函数,就像vprintf 所做的那样,那么您只需这样做。您可以以正常方式从va_listva_arg 中提取参数。

不要在va_list 上拨打va_startva_end:这是呼叫者的责任。由于无法正常重启va_list,如果需要多次扫描,则需要va_copy

这是一个简单的示例,仅用于说明(即它并不是最好的实现)。

这两个函数只是使用提供的分隔符字符串连接一堆字符串。第一个是“v”版本(如vsprintf),它实现了逻辑。第二个是 varargs 版本,它将 va_list 打包并传递给实现。

内部函数两次遍历参数;第一次添加字符串的大小。两个函数都返回一个新的 malloc 字符串,调用者需要释放该字符串。

参数列表必须以NULL 结束。

char* vjoin(const char* delim, va_list ap) {
  va_list aq;
  va_copy(aq, ap);
  size_t dlen = strlen(delim);

  /* First pass. Use the copied va_list */
  size_t needed = 1; /* NUL terminator */
  const char* s = va_arg(aq, const char*);
  if (s) {
    needed += strlen(s);
    while ((s = va_arg(aq, const char*)))
      needed += dlen + strlen(s);
  }
  va_end(aq);

  /* Second pass. Use the original va_list */
  char* rv = malloc(needed);
  size_t offset = 0;
  *rv = 0;
  s = va_arg(ap, const char*);
  if (s) {
    strcpy(rv, s);
    offset = strlen(s);
    while ((s = va_arg(ap, const char*))) {
      strcpy(rv + offset, delim);
      strcpy(rv + offset + dlen, s);
      offset += dlen + strlen(s);
    }
  }
  return rv;
}

char* join(const char* delim, ...) {
  va_list ap;
  va_start(ap, delim);
  char* rv = vjoin(delim, ap);
  va_end(ap);
  return rv;
}

【讨论】:

  • 我喜欢它,这是我发现的唯一一篇演示如何使用 va_list 参数编写函数的帖子。
  • 我认为这与第一次一样很棒 - 这可能要求 64 位系统/va_list 是数组类型的任何系统上的未定义行为 (UB)。在这种情况下,在调用vjoin 之前,C99 告诉我们ap 将被转换为typeof(*ap) *va_copy 需要 va_list 参数,即数组类型。为什么这很重要?在函数入口处,机器代码将导致每个va_list 参数(即va_list(它是一个数组类型)到typeof(*ap) *)的转换。这种转换在vjoin 内产生ap 的UB,因为ap 已经是typeof(*ap) *
  • 人们可能倾向于快速说“但是vprintf 接受va_list 参数,那么它还能如何工作?”。如果您尝试查看 gcc 实现,您会发现 vprintf 不是作为 C 函数编写的 - 它可能是汇编程序 - 并且与整个编译器实现相关。
  • @polynomial_donut:据我所知,vprintf 根本不在gcc 中;它是glibc 的一部分,它肯定是用 C: sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/… 编写的。 gcc 发行版在 libiberty 中包含一个 vprintf 版本,但它也是用 C 编写的,我认为它通常不被使用。如果您有其他信息,我很乐意看到。
  • @polynomial:至于您的疑问,也许可以通过检查 glibc 中 vfprintffprintf 的实际实现来判断:sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/… 无论如何,C 标准使很明显,我的示例中的用法是合法的:“如果需要访问不同的参数,则被调用的函数应声明一个类型为 va_list 的对象(在本子条款中通常称为 ap)。对象 ap 可以传递为另一个函数的参数;" (7.16p)。
【解决方案2】:

当然,最大的区别在于printf 在格式字符串之后采用可变数量的可选参数,而vprintf 采用一个“指向”所有参数的非可选参数。

printf 的大多数实现都会创建这个单一的数据结构,然后调用vprintf

阅读有关可变数量参数(也称为可变参数)的任何教程,了解它是如何工作的。 This printf (and family) referencethis variable arguments reference 也可能派上用场(其中有一个示例)。

【讨论】:

    【解决方案3】:

    诀窍是要意识到vprintf(或更可能是vfprintf)是实现的底层核心功能。 printf 可能只是一个包装器,它调用va_startvprintf,然后是va_end

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-13
      • 2020-09-30
      • 2011-09-16
      • 2012-12-15
      • 2012-06-26
      • 2010-09-07
      • 2016-11-05
      相关资源
      最近更新 更多