在C语言中,返回va_list是否安全?

16

我想编写一个返回类型为va_list的函数。

例如:va_list MyFunc(va_list args);

这种写法是否安全且可移植?


4
如果您的意图是传入一个va_list,修改它,然后在从函数返回时使用已修改的va_list,那么最好考虑将指向va_list的指针传递给MyFunct()并通过指针对列表进行操作。标准特别提到了这种技术在脚注中是可行的。 - Michael Burr
2
在我看来,va_list 不是一个好的想法。通常有更安全的更好的解决方案。因此,我会认真思考并得到更好的解决方案来设计和实现函数。 - Ed Heal
3
@Michael Burr 我看到了一个答案(https://dev59.com/f3A75IYBdhLWcg3wS3FA),我认为脚注如下:215)允许创建指向 va_list 的指针并将该指针传递给另一个函数,在这种情况下,原始函数可以在其他函数返回后继续使用原始列表。 - Hayri Uğur Koltuk
2
将指针传递给另一个函数与返回该指针是完全不同的。许多/大多数实现将实际变量参数存储在堆栈帧中,当可变参数函数返回时会销毁该堆栈帧。(即返回va_list或指向其的指针将使您拥有指向已销毁的局部变量的指针)。 - nos
在我的情况下,我会返回 va_list,但还是谢谢你的警告。 - Hayri Uğur Koltuk
显示剩余2条评论
4个回答

7
va_list可能(但不保证)是一个数组类型,因此您不能按值传递或返回它。看起来像这样的代码可能只是传递/返回指向第一个元素的指针,因此您可以在被叫方使用参数,但可能会对原始参数产生影响。
正式地说,va_list 可能是一个实体类型而非值类型。您可以使用 va_copy 进行复制,而不是通过赋值或函数参数/返回进行复制。

1

虽然您肯定可以返回这样的值,但我不确定返回值是否可以以有用的方式使用。

由于处理va_list需要特殊处理(在va_start()va_copy()之后需要va_end()),而且va_start/copyva_end宏甚至允许包含{ }来强制执行此配对,因此您不能单独调用其中一个。


3
va_copy用于创建第二个va_list对象,它可以独立于原始对象进行读取。由于这里不需要这样做,因此可以传递和返回va_list对象。此外,标准没有提到va_start()va_end()必须在同一范围内,因此我不明白它们如何能包含{ - Lindydancer
2
@Lindydancer 哦,不知道那个。在这里我读到“每次调用 va_start() 必须在同一函数中使用相应的 va_end() 调用匹配”和“每次调用 va_copy() 必须在同一函数中使用相应的 va_end() 调用匹配。”,所以我认为它来源于标准... - glglgl
@glglgl:“在同一个函数”并不一定意味着“在同一个作用域”,所以并不意味着宏能够使用不匹配的大括号。但是它确实允许 va_copy 使用 alloca 等技巧,因为新列表不会存在于函数返回之后。 - Steve Jessop

1

无论语言标准如何规定,实际上这种方法不太可能奏效。一个 va_list 很可能是指向调用者在堆栈上为被调用者放置的调用记录的指针。一旦被调用者返回,该堆栈上的内存就可以被重新使用。

返回类型 va_list 不太可能实际上将列表内容复制回调用者。虽然这是 C 的有效实现,如果标准要求这样做,那么这将是规范的缺陷。


va_end宏存在的原因正是因为va_list不一定只是指向堆栈上的某个指针。否则,va_end将始终是无操作的。存在这样的实现,其中在堆上分配了va_list对象。 - Dietrich Epp
@DietrichEpp 很好的信息,我以为 va_end 与寄存器窗口或仅允许一次迭代的旧实现有关。我调整了我的答案,但这只是相对重点的问题... 大多数架构不使用堆栈来处理可变参数。 - Potatoswatter

1
将指针传递给另一个函数与返回该指针是完全不同的。许多/大多数实现将实际变量参数存储在堆栈帧中,当可变参数函数返回时会被销毁。(即返回va_list或指向其的指针将使您拥有指向已销毁的局部变量的指针)。- nos
在我的情况下,我将返回va_list,但感谢您的警告。- Hayri Uğur Koltuk
如果您将指向va_list的指针传递给函数MyFunc(va_list *args),则无需将修改后(通过va_arg(*args, type))的参数列表传回,因为MyFunc会修改原始列表。

我认为并没有明确指定MyFunc是否修改原始列表。我肯定遇到过两种方式都可以工作的实现。 - supercat
@supercat - 如果将指向任何对象的指针传递给函数,并且该函数修改了指向的对象,则原始对象被修改是固有的。 - Armali
如果va_list是一个简单指针(在许多平台上都是最有效的),将其传递给函数将会给函数一个指针的副本,而va_arg将会增加该指针的副本而不影响原始指针。 - supercat
@supercat - 我们(可能除了你)正在讨论将指向va_list的指针传递给函数,因此如果va_list是一个简单的指针,则会传递指向该指针的指针,并且函数会修改原始指针所指向的内容。 - Armali
1
好的,我明白我之前的困惑了。在某些系统上,va_list 是一个数组,这种类型的参数 本质上 会被传递为指针,而在其他系统上则不是这样,但我现在看到,在你的特定情况下,你将 * 包含在参数类型中,因此它将被传递为单级间接指针(指向 va_list 中包含另一个指针的任何内容)。 - supercat

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