将va_list变量放入...变量参数列表(!)

时间:2015-06-23 21:21:53

标签: c variadic-functions

我想设计一个带有可变数量参数的函数,其中一个参数本身就是va_list;但是我的代码出了问题,我不明白是什么......

警告 - 我的问题不是设计代码做我想做的事情(我找到了绕过问题的方法),而只是关于理解我做错了什么...... < / p>

为了解释我的问题,让我们从一个简单的例子开始:即一个函数ffprintf,其作用类似fprintf,但将其内容写入多个字符串,其编号由第一个字符串表示ffprintf的参数,其身份由下一个参数给出(这些参数的数量可能因调用而异,因此您必须使用变量参数列表)。这样的函数将使用如下:

FILE *stream0, *stream1, *stream2;
int a, b;
ffprintf (3, stream0, stream1, stream2, "%d divided by %d worths %f", a, b, (double)a / b);

它的代码是:

void ffprintf (int z, ...)
 {va_list vlist, auxvlist;
  FILE **streams = malloc (z * sizeof(FILE *));
  va_start (vlist, z);
  for (int i = 0; i < z; ++i)
   {streams[i] = va_arg (vlist, FILE *); // Getting the next stream argument
   }
  char const *format = va_arg (vlist, char const *); // Getting the format argument
  for (int i = 0; i < z; ++i)
   {va_copy (auxvlist, vlist); // You have to work on a copy "auxvlist" of "vlist", for otherwise "vlist" would be altered by the next line
    vfprintf (streams[i], format, auxvlist);
    va_end (auxvlist);
   }
  va_end (vlist);
  free (streams);
 }

工作正常。现在,还有标准函数vfprintf,其原型为vfprintf (FILE *stream, char const* format, va_list vlist);,您可以像这样创建另一个具有可变参数列表的函数:

void fprintf_variant (FILE *stream, char const* format, ...)
 {
  va_list vlist;
  va_start (vlist, format);
  vfprintf (stream, format, vlist);
  va_end (vlist);
 }

这也很好。现在,我的目标是将两个想法结合起来创建一个我称之为vffprintf的函数,你可以这样使用它:

FILE *stream0, *stream1, *stream2;
void fprintf_onto_streams012 (char const *format, ...)
 {va_list vlist;
  va_start (vlist, format);
  vffprintf (3, stream0, stream1, stream2, format, vlist);
  va_end (vlist);
 }

我设计了以下代码:

void vffprintf (int z, ...)
 {va_list vlist, auxvlist, auxauxvlist;
  va_start (vlist, z);
  FILE **streams = malloc (z * sizeof(FILE *));
  for (int i = 0; i < z; ++i)
   {streams[i] = va_arg (vlist, FILE *);
   }
  char const *format = va_arg (vlist, char const *);
  va_copy (auxvlist, va_arg (vlist, va_list)); // Here I get the next argument of "vlist", knowing that this argument is of "va_list" type
  for (int i = 0; i < z; ++i)
   {va_copy (auxauxvlist, auxvlist);
    vfprintf (streams[i], format, auxvlist);
    va_end (auxauxvlist);
   }
  va_end (auxvlist);
  va_end (vlist);
  free (streams);
 }

这段代码编译顺利,但它无法正常工作......例如,如果我编写以下完整代码:

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

void vffprintf (int z, ...)
 {va_list vlist, auxvlist, auxauxvlist;
  FILE **streams = malloc (z * sizeof(FILE *));
  va_start (vlist, z);
  for (int i = 0; i < z; ++i)
   {streams[i] = va_arg (vlist, FILE *);
   }
  char const *format = va_arg (vlist, char const *);
  va_copy (auxvlist, va_arg (vlist, va_list));
  for (int i = 0; i < z; ++i)
   {va_copy (auxauxvlist, auxvlist);
    vfprintf (streams[i], format, auxauxvlist);
    va_end (auxauxvlist);
   }
  va_end (auxvlist);
  va_end (vlist);
  free (streams);
 }

void printf_variant (char const *format, ...)
 {va_list vlist;
  va_start (vlist, format);
  vffprintf (1, stdout, format, vlist);
  va_end (vlist);
 }

int main (void)
 {printf_variant ("Ramanujan's number is %d.\n", 1729);
  return 0;
 }

我遇到了段错!...为什么?!

P.-S:很抱歉这个很长的问题;但我希望它非常清楚,因为它技术性很强......

P.-S.2:我故意用这个问题标记“va-list”和“variableargumentlists”,因为我感兴趣的是va_list被视为一种类型 ,在(其他)变量参数列表中,被视为列表 ...所以这些实际上是两个不同的概念。

3 个答案:

答案 0 :(得分:2)

C11(N1570)最终草案中va_arg的描述包含( type 是第二个参数):

  

如果类型与实际下一个参数的类型不兼容(根据默认参数促销提升),则行为未定义

va_list被允许是一个数组类型(标准要求它是一个所谓的“完整对象类型”),看起来你的实现利用了这种可能性。您可能知道在C数组中,当它们衰减成指针时,它们不能作为参数传递,并且这种指针的类型与原始数组类型不兼容。

例如:int *int [1]不兼容。因此,如果您确实需要以便携式方式传递数组或va_list,请定义struct成员va_list并传递该成员(请参阅Why can't we pass arrays to function by value?)。

答案 1 :(得分:1)

void vffprintf (int z, ...)
 {
  //...
  va_copy (auxvlist, va_arg (vlist, va_list));//this line has the problem 
   //...
 } 

像这样快速而棘手的方式,它会起作用。

  void vffprintf (int z, ...)
 {
  //...
  va_copy (auxvlist, va_arg (vlist, void*));
   //...
 }

以下是有关var_argva_list的一些参考资料,应该提供详细而详尽的解释。

1)Pass va_list or pointer to va_list?

2)Is GCC mishandling a pointer to a va_list passed to a function?

3)What is the format of the x86_64 va_list structure?

希望他们有所帮助。

答案 2 :(得分:1)

如果要使用va_arg()检索它,可能需要将类型va_list包装到结构中:

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

typedef struct
{
    va_list list ;  
} my_list ;

void vffprintf (int z, ...)
 {my_list vlist, auxvlist, auxauxvlist;
  FILE **streams = malloc (z * sizeof(FILE *));
  va_start (vlist.list, z);
  for (int i = 0; i < z; ++i)
   {streams[i] = va_arg (vlist.list, FILE *);
   }
  char const *format = va_arg (vlist.list, char const *);
  my_list parent = va_arg (vlist.list, my_list) ;
  va_copy (auxvlist.list, parent.list );
  for (int i = 0; i < z; ++i)
   {va_copy (auxauxvlist.list, auxvlist.list);
    vfprintf (streams[i], format, auxauxvlist.list);
    va_end (auxauxvlist.list);
   }
  va_end (auxvlist.list);
  va_end (vlist.list);
  free (streams);
 }

void printf_variant (char const *format, ...)
 {my_list vlist;
  va_start (vlist.list, format);
  vffprintf (1, stdout , format, vlist);
  va_end (vlist.list);
 }

int main (void)
 {printf_variant ("Ramanujan's number is %d.\n", 1729 );
  return 0;
 }

问题源于数组和相同类型的指针不兼容,并且va_list被定义为数组。然后你尝试得到那种类型:

va_arg (vlist, va_list)

所以你告诉va_arg你得到一个数组但是如果传递的va_list已经衰减到一个指针。您应该使用va_list的指针版本,但是您首先不知道va_list的真实定义,因此您无法获得它的指针版本。

解决方案是将va_list包装到您控制的类型中,即结构。