2015-06-23 81 views
5

我想设计一个函数,它带有可变数量的参数,其中一个参数本身就是va_list;但我的代码中出现错误,我不明白是什么...把一个va_list变量放在一个变量参数列表(!)里面

警告 - 我的问题不是关于设计一个代码做我想做的事情(我找到了一种方法来绕过这个问题),但只有理解我做错了什么......

解释我的问题,让我们先从一个简单的例子,即:一个功能ffprintf这就像fprintf,但是写的内容下载到若干字符串,字符串,其数量为由ffprintf的第一个参数指示,其身份由下一个参数给出(这些参数的数量可能因呼叫而异,因此您必须使用变量argum ent list)。这样的功能会被这样使用:

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,看作是类型,在(其他)变量参数列表中,被看作列表 ...因此,这些实际上是两个不同的概念。

+0

所以问题是为什么最后一个例子失败? – this

+0

是的,这正是:-) –

+0

如果您忽略内存泄漏,看起来很好。 – this

回答

2

va_arg在C11(N1570)的最后草案说明包含(类型是第二个参数):

如果类型是不符合实际的下一个的类型兼容参数(根据默认参数促销推广),行为未定义

va_list允许为数组类型(th e标准要求它是一个所谓的“完整对象类型”),看起来你的实现使用了这种可能性。您可能知道在C数组中,不能将它们作为参数传递,因为它们会衰减为指针,并且此类指针的类型与原始数组类型不兼容。例如:int *int [1]不兼容。所以如果你真的需要传递一个数组或者一个va_list,那么用一个va_list成员来定义一个struct并且通过它(见Why can't we pass arrays to function by value?)。

+0

Hello cremno, 我还没有掌握所有的细节,但我有一个印象,你的观点与标准中的下列子句相联系:“参数'type'是一个指定的类型名称,因此指针的类型到一个具有指定类型的对象可以简单地通过给'type'添加'*'来获得。但是没有什么能保证'va_list'会是这样一种类型,例如数组类型不起作用......它是正确的吗? 另一方面,这似乎并不能解释Eric Tsui的“void *”技巧是如何工作的...... –

+1

@ Nancy-N:这是一个**技巧**。但他必须解释这一点,因为这是他答案的一部分。它可能会在你永远关心的任何地方工作。这并不会改变它是UB的事实(他发布的描述也是错误的)。 [这里](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50581)是海湾合作委员会的一个错误报告,他的记者想要做类似的事情,甚至与你一样。它甚至在C90中提到了一个边缘案例!也许阅读它会清除比我的答案更多的东西。它还提到使用结构作为可能的解决方案。 – cremno

+0

@cremno,感谢您的纠正。所以,在不同的'va_list'实现下,我对'va_list'和'va_list *'做了更多的研究,这确实是个问题。 –

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?

希望它们是有帮助的。

+1

确实有效;了不起!:-D如果你有更详细的解释,我很想听到它:为什么前一行失败?为什么新的工作?它这个“无效*技巧”是否便携?... –

+0

对我而言还不清楚。当'printf_variant'调用'vffprintf'时,'vffprintf'的变量参数列表是{'stdout','format','vlist_0'},'vlist_0'是'printf_variant'的变量参数列表,即{'1929 '}。所以,当调用'va_arg(vlist_1,va_list)'(我已经在后缀'_0'和'_1'中设置了两个'vlist'清除之间的区别时),'vlist_1'的下一个参数是{'1929'}具体可能是'struct {char *,int}'。但是这不是一个整数,是吗?......无论如何,这怎么可能转化为一个'void *'?! 对不起,没有更容易理解... –

+1

@ Nancy-N,我试图做的就是这样'va_copy(auxvlist,va_arg(vlist,va_list *))',但它会给出警告。所以,我把它改成''va_copy(auxvlist,va_arg(vlist,void *))'' –

1

您可能需要包装类型va_list成一个结构,如果你想使用的va_arg()来检索它:

#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包装成您控制的类型,一个结构。

+0

@ this - 谢谢你绕过这个问题(我不知道)和写下代码的方式! :-) –

+0

@ Nancy-N那么这段代码是否确实解决了这个问题? – this

+0

@这 - 确实 - 至少在我自己的机器上;-) –