如何使用不确定数量的参数和 va_list 来组合参数?

How to make function to combine parameters using indeterminate number of parameters and va_list?

我想用这个原型做一个函数:

char *combine(const char *string, ...)

将命令行给出的参数和 returns 结果合并为 main

我的函数目前看起来像这样:

char *combine(const char *mj, ...)
{
     va_list pl;
     va_start(pl, mj);

     while(mj != NULL) {
        printf("%s",mj);
        mj = va_arg(pl, char *);
     }

     va_end(pl);
     return pl;
}

例如,如果用户 运行 使用以下命令运行此程序:

./a.out one two three four five six seven

输出应该是:

Parameter: onetwothreefourfivesixseven (length of parameter: 27)

现在如果我 运行 我的程序,输出主要是随机元素,参数只是空的。

出了什么问题,我需要修复什么才能显示正确的内容?

该程序需要使用以下 main 代码:

int main(int argc, char *argv[]) {
  int i = 0;
  char *mj = malloc(1);

  mj[0] = '[=15=]';
  for(i = 1; (i+3) <= argc; i += 3)
  { 
    char *tmp = mj;
    mj = combine(tmp, argv[i], argv[i+1], argv[i+2], NULL);
    free(tmp);
  }
  if((i+2) == argc)
  {
    char *tmp = mj;
    mj = combine(mj, argv[i], argv[i+1], NULL);
    free(tmp);
  }
  else if((i+1) == argc)
  {
    char *tmp = mj;
    mj = combine(mj, argv[i], NULL);
    free(tmp);
  }
  printf("Parameter: %s (length: %lu)\n", mj, (unsigned long)strlen(mj));
  free(mj);
  return 0;
}

va_list 视为不透明类型,如 "never mind what it really is"。 "functions"、va_start()va_arg()va_end()其实不是函数,而是宏。同样,一开始它们到底是什么以及它们实际如何工作并不那么重要。 ("Pay no attention to that man behind the curtain!")您现在需要知道的是如何使用它们。

假设您正在编写自己的函数:

char *combine(const char *mj, ...)

有一个参数 const char *mj,然后...可能还有其他一些。那么,如果这些参数没有标识符,而且您甚至不知道它们有多少,您如何引用这些参数呢?这就是 va_list 的用途。可以把它想象成一个游标,可以滚动浏览这个参数列表,并且可以以某种方式告诉你下一个参数是什么。

首先,你必须设置 va_start:

va_start(pl, mj);

这意味着使用 mj 设置 va_list pl 作为 最后已知的显式参数

第一次使用 va_arg()pl 时,它会给你第一个参数 after mj。您还需要在 va_arg() 中指定下一个参数的预期类型,因此在您的情况下,调用将是 va_arg(pl, char *)。每次您使用 va_arg() 时,它都会推进此 "cursor",以便下一次调用 va_arg() 时会为您提供下一个参数。所以每次你使用 va_arg() 时,你都应该用这个值做一些事情,因为你不会再得到它了。 (除非,也许你重新开始。)

那么你怎么知道你是否在列表的末尾?好吧,您只需要以某种方式知道即可。 va_arg() 不会告诉您您在列表的末尾,如果您继续使用它,它会很乐意继续越过末尾。通常,您会使用最后一个已知参数(在您的情况下 mj)以某种方式指定有多少个参数,或者您可以在参数列表中查找一些标记值。

当您决定完成后,请务必使用

va_end(pl);

此后,va_list pl 无效,不应再次引用。 (除非你想从另一个 va_start() 重新开始。)


经典的 printf() 示例说明了如何使用它,它使用可变参数。从 man pages,我们知道 printf() 被声明为

int printf(const char *format, ...);

考虑,例如函数调用

printf("The value of %s is %ld\n", lbl, x);

在内部,printf() 会使用一些 va_list,我们称之为 vlformat 是最后一个已知参数,因此它会用在 va_start(vl, format) 中。 printf() 将解析此 format 字符串,找到 %s 并期望接下来有一个 char * 参数。然后它会调用 va_arg(vl, char *),以某种方式使用这个值,并继续解析 format 字符串。然后它找到 %ld 并期望一些 long int,因此它调用 va_arg(vl, long int) 并在打印时使用该值。再解析format后,到了格式串的末尾,就停止了,最终使用va_end(vl)。 (如果您留心的话,也许现在您可以弄清楚为什么始终在 printf 格式字符串中使用正确的长度修饰符和转换说明符很重要。)


这就是如何使用一个va_list。现在回到您的代码:请记住 va_start(pl, mj); 确实 而不是 将任何内容分配给 mj,正如您可能认为的那样。它仅使用 mj 设置 va_list pl 作为最后一个已知的显式参数。您还出于某种原因重新分配 mj (该部分没有意义)。您甚至根本没有尝试连接任何字符串。看起来你只是想打印它们。从某个大小的动态分配的缓冲区开始,将字符串一个一个地附加到它,然后根据需要重新分配。 (这并不难,但第一次做时会显得不平凡。如何实现它超出了本题的范围。)

va_list 没有 "combine" 任何东西,它一次只给你一个函数的参数(它更像是把东西分开)。你必须自己进行组合,va_list 就是如何得到碎片。