我在MSVC v142中运行这个函数。我需要保留多个va_list
,然后将它们以数组的形式传递给另一个API。但是,旧的va_list
被新的va_list
覆盖了。
void toVaList(va_list *out, int count, ...)
{
va_list args;
va_start(args, count);
vprintf("toVaList: %d %d\n", args);
*out = args;
}
int main()
{
va_list args1;
toVaList(&args1, 2, 11, 22);
vprintf("first va_list: %d %d\n", args1);
va_list args2;
toVaList(&args2, 2, 33, 44);
vprintf("first va_list: %d %d\n", args1); //now args1 are overwritten
vprintf("second va_list: %d %d\n", args2);
va_end(args1);
va_end(args2);
return 0;
}
输出为
toVaList: 11 22
first va_list: 11 22
toVaList: 33 44
first va_list: 33 44
second va_list: 33 44
是否有可能解决此问题?
编辑:
现在memcpy
很适合我,我把va_list
复制到一个单独的内存中,把原来的va_list
保存在由va_start
和va_end
Package 的函数体中,看起来复制到函数体外的va_list
工作得很好。vprintf
不会使va_list
参数无效,因为它可能在其实现内部生成了va_copy
。
void toVaList1(va_list *out, int count, ...)
{
va_list args;
va_start(args, count);
memcpy(out, args, 256);
vprintf("toVaList1: %d %d\n", args);
va_end(args);
}
int main()
{
char args1[256];
toVaList1((va_list *)args1, 2, 11, 22);
vprintf("first va_list: %d %d\n", args1);
char args2[256];
toVaList1((va_list *)args2, 2, 33, 44);
vprintf("first va_list: %d %d\n", args1);
vprintf("second va_list: %d %d\n", args2);
char args3[256];
toVaList1((va_list *)args3, 2, 55, 66);
vprintf("first va_list: %d %d\n", args1);
vprintf("second va_list: %d %d\n", args2);
vprintf("third va_list: %d %d\n", args3);
}
输出为
toVaList1: 11 22
first va_list: 11 22
toVaList1: 33 44
first va_list: 11 22
second va_list: 33 44
toVaList1: 55 66
first va_list: 11 22
second va_list: 33 44
third va_list: 55 66
编辑:
简单描述一下这个需求的场景,我们有一个用XML定义id的消息列表,还有一个内部API,它把msgId和va_list作为参数,使用va_arg()来读取va_list:
ID Message
1 This is a message containing integer %d and string %s.
2 This is another message containing double %f.
.....
void internalAPI_PrintMsg(int msgId, va_list ap)
{
char resultMsg[512];
//lookup message id in XML and substitute arguments
//in the loop through found message body...
switch(char)
{
case 'd':
int i = va_arg(ap, int);
//substitute the integer
break;
case 'f':
double d = va_arg(ap, double);
//substitute the double
break;
....
}
PrintResultMessage(resultMsg);
}
我们已经有了打印单个消息的API:
void printMsg(int msgId, ...)
{
va_list args;
va_start(args, msgId);
internalAPI_PrintMsg(msgId, args);
va_end(args);
}
现在我们需要支持一个API来打印一组消息:
typedef struct
{
long id;
char[256] args;
} msgData;
void printGroupMsg(msgData * msgArr, int msgCount) //this is the new API that requires multiple va_list
{
//do some other stuff to start group messaging
for (int i = 0; i < msgCount; ++i)
internalAPI_PrintMsg(msgArr[i].id, msgArr[i].args);
//do some other stuff to end group messaging
}
void initMsgData(msgData *msg, int msgId, ...) //this is equivalent to toVaList(...)
{
msg->id = msgId;
va_list args;
va_start(args, msgId);
memcpy(msg->args, args, 256);
va_end(args);
}
//client code:
msgData *arr = malloc(sizeof(msgData)*2);
initMsgData(arr, 1, 88, "test");
initMsgData(arr+1, 2, 123.456);
printGroupMsg(arr, 2);
2条答案
按热度按时间kmpatx3s1#
注意,C标准规定,如果在函数中调用
va_start
,则还必须在同一函数中调用va_end
:§7.16.1变量参数列表访问宏
§ 1本条款中描述的
va_start
和va_arg
宏应该作为宏而不是函数来实现。va_copy
和va_end
是宏还是用外部链接声明的标识符是不确定的。如果为了访问实际的函数而抑制了宏定义,或者程序定义了同名的外部标识符,行为未定义。* 每次调用va_start
和va_copy
宏时,应与同一函数中va_end
宏的相应调用相匹配。*您的
toVaList
函数违反了该要求,因此调用了未定义的行为。第7.16节的序言说:
3声明的类型为
其是适合于保存宏
va_start
、va_arg
、va_end
和va_copy
所需的信息的完整对象类型。如果期望访问变化自变量,被调用函数应声明一个对象(在本子句中通常称为ap
),具有类型va_list
。对象ap
可以作为参数传递给另一个函数;如果该函数使用参数ap
调用va_arg
宏,则调用函数中ap
的值是不确定的,并且应在进一步引用ap
之前传递给va_end
宏。253)253)允许创建指向
va_list
的指针并将该指针传递给另一个函数,在这种情况下,原始函数可以在另一个函数返回之后进一步使用原始列表。8oomwypt2#
必须使用
va_copy
代替*out = args;
来制作va_list
的副本,但它不会更改枚举va_list
的有效作用域,该作用域是被调用的函数。在函数toVaList
返回后引用va_list
具有未定义的行为。这个限制在C标准中没有明确规定。相关的语言是这样的:
7.16.1变量参数列表访问宏
...
va_start
和va_copy
宏的每次调用应与同一函数中va_end
宏的相应调用相匹配。这个短语意味着
va_end()
必须在为同一个va_list
参数调用va_start
或va_copy
的同一个函数中调用,因此一旦函数返回,就不能使用这个参数。您的方法在C标准中有未定义的行为。从实现的Angular 来看,这很容易理解:调用一个具有可变数量参数的函数涉及到以一种实现定义的方式将这些参数传递给函数,该方式是可变参数访问宏所抽象的。2然而,像任何其他函数调用一样,当函数返回到其调用者时,这些参数会超出作用域。3如果你想在稍后访问它们,你必须枚举它们并将它们的值保存到一个具有适当范围和生存期的数组或结构中。
memcpy
黑客试图这样做,但其行为未定义。下面是一个替代方法:
您可以通过将类型信息作为参数提供给
array_from_args()
(如printf
字符串),并构造标记结构的数组,将此方法推广到不同类型的参数。