填充 va_list

28

有没有办法从头开始创建一个 va_list?我想调用一个以 va_list 作为参数的函数:

func(void **entry, int num_args, va_list args, char *key); 

...从不接受可变数量参数的函数中获取参数。我能想到的唯一方法是创建一个中介函数,接受可变数量的参数,然后将它的va_list传递下去,这种做法相当愚蠢:

void stupid_func(void **entry, char *key, int num_args, ...) {
    va_list args;
    va_start(args, num_args);

    func(entry, num_args, args, key);

    va_end(args);
}

有更好的方法吗?我无法更改func的参数。


cocoawithlove上有一篇有用的博客文章,建议一种伪造va_list的方法 - 正如其他好的答案所指出的那样,它确实限制了您的编译器和平台特定行为。 - petert
7
“不要传递 va_list” 的评论已经过时,而且完全是错误的;va_list 存在的部分原因就是让你可以这样做。传递 va_list 是编写 printf 包装器的方法:你可以制作一个接受“...”的版本,然后将生成的 va_list 传递给“vfprintf”或其他函数。 - EvanED
值得明确的是:它取决于你传递的方向。将其从调用者“向下”传递给被调用者当然是正确的方法,也是每个人一直在做的方法。任何尝试从被调用者“向上”传递它(例如通过返回va_list、将其存储到全局/静态变量中)都会导致崩溃。同样,这对于任何中级 C 程序员都应该非常明显。 - pfalcon
4个回答

14

这是一个糟糕的想法,因为va_list抽象层存在于隐藏一些与堆栈指针相关的严峻编译器/架构细节。而且一旦初始化后,它基本上绑定到函数作用域。如果您在超出范围的情况下引用以前帧的va_args,则可能会出现问题。您可以传递它们,但是...

期望有错误

参见:http://lists.freebsd.org/pipermail/freebsd-amd64/2004-August/001946.html

此外,请查看man(3) va_copy和其它相关的内容,以更加安全地处理va_args并传递它们。

我个人认为va_args不是非常简洁。在过去,我曾通过在堆上初始化结构体/不透明指针,然后使用指针算术来处理数据来解决这个问题。但这是一个hack,并取决于情况。


1
在我看来,这个答案从根本上是错误的,甚至会误导人。链接的电子邮件谈到了两次使用va_list*,但它并没有说使用va_list就像OP指出的那样是错误的(实际上并不是)。 - ntd
@ntd建议进行编辑或至少在批评中具有建设性。我写这篇文章是基于我的经验。我可能是错的,但我很乐意接受。 - Aiden Bell
据我所知,维基是用于改善答案,而不是扭曲其意义的...我不能编辑它。我应该补充说,我来到这里是因为我正在寻找同样的信息:如果您知道更好的突出显示我发现的方法,我将很高兴删除我的无建设性评论。 - ntd

6

我理解并同意Aiden的警告——va_list及其相关函数很危险,因为它们隐藏了低级别的调用约定。但是,在这种情况下,我认为你别无选择。将static ...函数放入.c文件中,以便其他人看不到它,作为需要调用的函数的代理,对它进行充分的测试,然后就完成了。只要确保不向调用链上传递可变参数即可。


@Nikolai - 好建议,尽管我认为为了可移植性和优雅的缘故,将目标参数抽象成二进制块并在其上叠加一些结构信息是有必要的。除非最终需要使用类似于vsprintf的函数获取数据并且需要va_list。 - Aiden Bell
@Aiden - 在这里看起来就是这样 - OP说“我无法更改func的签名”。在将未知数量的内容传递到函数的一般情况下 - 是的,请务必使用一些明确定义且自我描述的结构(当然,通过指针)。 - Nikolai Fetissov

5
您的代理函数创建va_list的想法是正确的。这个代理不需要公共作用域。但是,您可能会发现代理已经存在。例如,在许多库实现中,sprintf()只是vsprintf()的代理。
除非您愿意将代码绑定到特定的编译器和目标平台,否则没有更好的方法。 <stdarg.h>中定义的名称提供了便携式和一致的接口,以支持访问可变参数列表。实现和使用可变参数函数的唯一便携式方式是通过该接口。
话虽如此,您可以通过在数组中复制调用帧并手动构建正确引用它的va_list来牺牲可移植性。结果永远不会是可移植的。

5
你的stupid_func是完全有效的C代码,否则你怎么可能调用vprintf和类似的函数呢? glib广泛使用这些包装器。 C99规范本身在示例中也有类似的内容。摘自第7.19.6.8节:

The following shows the use of the vfprintf function in a general error-reporting routine.

#include <stdarg.h>
#include <stdio.h>
void error(char *function_name, char *format, ...)
{
    va_list args;
    va_start(args, format);
    // print out name of function causing error
    fprintf(stderr, "ERROR in %s: ", function_name);
    // print out remainder of message
    vfprintf(stderr, format, args);
    va_end(args)
}

根据你在我的评论后的回复,我找到了你的答案。+1,特别是指出了传递va_list的有效示例。根据我的有限经验,做更多的事情会导致错误,在va_end之后或帧弹出后仍然引用参数。 - Aiden Bell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接