C变元函数:我需要保留多个va_list,但旧的会被最新的覆盖

pu3pd22g  于 2023-02-15  发布在  其他
关注(0)|答案(2)|浏览(155)

我在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_startva_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);
kmpatx3s

kmpatx3s1#

注意,C标准规定,如果在函数中调用va_start,则还必须在同一函数中调用va_end
§7.16.1变量参数列表访问宏
§ 1本条款中描述的va_startva_arg宏应该作为宏而不是函数来实现。va_copyva_end是宏还是用外部链接声明的标识符是不确定的。如果为了访问实际的函数而抑制了宏定义,或者程序定义了同名的外部标识符,行为未定义。* 每次调用va_startva_copy宏时,应与同一函数中va_end宏的相应调用相匹配。*

  • [.着重号后加.]*

您的toVaList函数违反了该要求,因此调用了未定义的行为。
第7.16节的序言说:
3声明的类型为

va_list

其是适合于保存宏va_startva_argva_endva_copy所需的信息的完整对象类型。如果期望访问变化自变量,被调用函数应声明一个对象(在本子句中通常称为ap),具有类型va_list。对象ap可以作为参数传递给另一个函数;如果该函数使用参数ap调用va_arg宏,则调用函数中ap的值是不确定的,并且应在进一步引用ap之前传递给va_end宏。253)
253)允许创建指向va_list的指针并将该指针传递给另一个函数,在这种情况下,原始函数可以在另一个函数返回之后进一步使用原始列表。

8oomwypt

8oomwypt2#

必须使用va_copy代替*out = args;来制作va_list的副本,但它不会更改枚举va_list的有效作用域,该作用域是被调用的函数。在函数toVaList返回后引用va_list具有未定义的行为。
这个限制在C标准中没有明确规定。相关的语言是这样的:

7.16.1变量参数列表访问宏

...
va_startva_copy宏的每次调用应与同一函数中va_end宏的相应调用相匹配。
这个短语意味着va_end()必须在为同一个va_list参数调用va_startva_copy的同一个函数中调用,因此一旦函数返回,就不能使用这个参数。
您的方法在C标准中有未定义的行为。从实现的Angular 来看,这很容易理解:调用一个具有可变数量参数的函数涉及到以一种实现定义的方式将这些参数传递给函数,该方式是可变参数访问宏所抽象的。2然而,像任何其他函数调用一样,当函数返回到其调用者时,这些参数会超出作用域。3如果你想在稍后访问它们,你必须枚举它们并将它们的值保存到一个具有适当范围和生存期的数组或结构中。memcpy黑客试图这样做,但其行为未定义。
下面是一个替代方法:

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

int **array_from_args(int count, ...) {
    va_list ap;
    va_start(ap, count);
    int *a = malloc(sizeof(*a) * (1 + count));
    if (a) {
        a[0] = count;
        for (int i = 0; i < count; i++) {
            a[i + 1] = va_arg(ap, int);
        }
    }
    va_end(ap);
    return a;
}

void print_array(const char *s, const int *a) {
    printf("%s:", s);
    if (a) {
        for (int i = 0, n = a[0]; i < n; i++)
            printf(" %d", a[i + 1]);
    }
    printf("\n");
}

int main() {
    int *args1 = array_from_args(2, 11, 22);
    int *args2 = array_from_args(2, 33, 44);
    int *args3 = array_from_args(2, 55, 66);
    print_array("args1", args1);
    print_array("args2", args2);
    print_array("args3", args3);
    free(args1);
    free(args2);
    free(args3);
}

您可以通过将类型信息作为参数提供给array_from_args()(如printf字符串),并构造标记结构的数组,将此方法推广到不同类型的参数。

相关问题