C 将单个参数列表传递给两个v*printf()调用

C 将单个参数列表传递给两个v*printf()调用,c,printf,portability,autoconf,C,Printf,Portability,Autoconf,考虑以下测试用例: #define _GNU_SOURCE #include <stdio.h> #include <stdarg.h> void test(char **outa, char **outb, const char* fstra, const char* fstrb, ...) { va_list ap; va_start(ap, fstrb); vasprintf(&outa, fstra, ap); vasp

考虑以下测试用例:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdarg.h>

void test(char **outa, char **outb, const char* fstra, const char* fstrb, ...) {
    va_list ap;

    va_start(ap, fstrb);
    vasprintf(&outa, fstra, ap);
    vasprintf(&outb, fstrb, ap);
    va_end(ap);
}

int main(void) {
    char *a, *b;
    test(&a, &b, "%s", " %s\n", "foo", "bar");
    /* ... */
}
定义GNU源
#包括
#包括
无效测试(字符**outa,字符**outb,常量字符*fstra,常量字符*fstrb,…){
va_列表ap;
va_启动(ap、fstrb);
vasprintf(&outa、fstra、ap);
vasprintf(&outb、fstrb、ap);
va_端(ap);
}
内部主(空){
字符*a,*b;
测试(&a、&b、“%s”、“s\n”、“foo”、“bar”);
/* ... */
}
这里的意图是,
test()
函数接受两个格式字符串和两个字符串的参数列表。第一个格式字符串应该“吃”它所需要的参数,其余的应该用于第二个格式字符串

因此,这里的预期结果将是
foo
&
bar
,这就是我使用glibc得到的结果。但是AFAICS运行codepad的机器(猜测是某些*BSD),给出了
foo
&
foo
,我猜它在参数列表上使用了
va_copy()

我猜我在这里碰到了一个未定义的(丑陋的)行为;所以问题是:有没有一种方法可以实现双格式字符串
printf()
,而无需从头开始重新实现?是否有一种很好的方法可以在不使用
AC\u RUN\u IFELSE()
的情况下使用autoconf检查该行为


我想一些快速扫描格式字符串以获取要使用的参数数量的方法也可以在这里使用(+
va_copy()
)。

当您调用其中一个
v*printf
函数时,它使用
va_arg
,这意味着
ap
的值在返回时是不确定的

相关位位于C99中vfprintf函数的
7.19.6.8节,该节引用了脚注:

当函数vfprintf、vfscanf、vprintf、vscanf、vsnprintf、vsprintf和vsscanf调用
va_arg
宏时,返回后
arg
的值是不确定的。

这已经保存到最新的C1x草案,我也有,所以我怀疑它不会很快改变

使用更高级的
v*printf
函数没有可移植的方法来完成您正在尝试的操作,尽管您可以求助于使用较低级的东西

该标准非常明确,在
va_列表
变量上使用
va_arg
的被调用函数会使其在调用方中不确定。从C99
7.15变量参数

对象ap可以作为参数传递给另一个函数;如果该函数使用参数ap调用va_arg宏,则调用函数中ap的值是不确定的,在进一步引用ap之前,应将其传递给va_end宏。

但是,在单个函数中对其使用
va_arg
时,
ap
的值是确定的(否则整个变量参数处理将崩溃)。因此,您可以编写一个函数,使用这些较低级别的函数依次处理两个格式字符串

对于更高级别的内容(根据脚注),您需要
va_end/va_start
ap
变量重新设置为确定状态,不幸的是,这将重置为参数列表的开头

我不确定您提供的示例代码简化了多少,但是,如果这接近实际情况,您可以通过预先组合两个格式字符串并使用它们传递到
vprintf
,获得相同的结果,例如:

void test(const char* fstra, const char* fstrb, ...) {
    char big_honkin_buff[1024]; // Example, don't really do this.
    va_list ap;

    strcpy (big_honkin_buff, fstra);
    strcat (big_honkin_buff, fstrb);

    va_start(ap, big_honkin_buff);
    vprintf(big_honkin_buff, ap);
    va_end(ap);
}

调用其中一个
v*printf
函数时,将使用
va_arg
,这意味着
ap
的值在返回时是不确定的

相关位位于C99中vfprintf函数的
7.19.6.8节,该节引用了脚注:

当函数vfprintf、vfscanf、vprintf、vscanf、vsnprintf、vsprintf和vsscanf调用
va_arg
宏时,返回后
arg
的值是不确定的。

这已经保存到最新的C1x草案,我也有,所以我怀疑它不会很快改变

使用更高级的
v*printf
函数没有可移植的方法来完成您正在尝试的操作,尽管您可以求助于使用较低级的东西

该标准非常明确,在
va_列表
变量上使用
va_arg
的被调用函数会使其在调用方中不确定。从C99
7.15变量参数

对象ap可以作为参数传递给另一个函数;如果该函数使用参数ap调用va_arg宏,则调用函数中ap的值是不确定的,在进一步引用ap之前,应将其传递给va_end宏。

但是,在单个函数中对其使用
va_arg
时,
ap
的值是确定的(否则整个变量参数处理将崩溃)。因此,您可以编写一个函数,使用这些较低级别的函数依次处理两个格式字符串

对于更高级别的内容(根据脚注),您需要
va_end/va_start
ap
变量重新设置为确定状态,不幸的是,这将重置为参数列表的开头

我不确定您提供的示例代码简化了多少,但是,如果这接近实际情况,您可以通过预先组合两个格式字符串并使用它们传递到
vprintf
,获得相同的结果,例如:

void test(const char* fstra, const char* fstrb, ...) {
    char big_honkin_buff[1024]; // Example, don't really do this.
    va_list ap;

    strcpy (big_honkin_buff, fstra);
    strcat (big_honkin_buff, fstrb);

    va_start(ap, big_honkin_buff);
    vprintf(big_honkin_buff, ap);
    va_end(ap);
}

正如另一个答案已经指出的那样,将
ap
传递给v*()函数会使
ap
处于未确定状态。因此,解决办法是不依赖于这种状态。我建议另一种解决办法

首先,将ap初始化为正常。然后使用
vsnprintf(NULL,0,fstra,a)确定第一个格式化字符串的长度