在 C++ 中传递 va_list 作为对函数的引用是否合法?

use*_*229 15 c++ pass-by-reference variadic-functions

在代码审查/clang-tidy 运行时,我遇到了一个具有如下签名的函数:

void appendFoo(const char * fmt, va_list& rVaList);
Run Code Online (Sandbox Code Playgroud)

我以前从未见过这个。Afaik,您可以va_list按值传递(也可以通过指针传递?)。

这引出了我的第一个问题:通过va_list引用传递合法吗?它是否按预期工作?

不管怎样,appendFoo()调用vsprintf()它的定义和 clang-tidy 给出了以下警告:Function 'vsprintf' is called with an uninitialized va_list argument [clang-analyzer-valist.Uninitialized]。看起来的定义appendFoo()基本上是这样的:

void appendFoo(const char * fmt, va_list& rVaList) {
  // retracted: allocate buffer

  vsprintf(buffer, fmt, rVaList);

  // errorhandling
  // de-allocate buffer
}
Run Code Online (Sandbox Code Playgroud)

(是的, 的返回值vsprintf被忽略,错误以另一种方式“处理”。我正在修复它......)

特别是,不va_copy调用va_start、 等。

删除引用va_list并按值传递,即将签名更改为void appendFoo(const char * fmt, va_list rVaList);,消除了 clang-tidy 警告。

这引出了我的第二个问题:如果通过引用传递,clang-tidy 产生的警告是否是va_list误报,或者作为引用va_list传递实际上是否存在问题?


PS:这个问题不是varargs (va_list va_start) does not work with pass-by-reference argument 的重复。该问题的OP通过引用采用va_list的函数来传递参数。我问的是 va_list 本身作为参考传递。那好吧。

Jan*_*tke 2

是的,这是允许的。首先,[cstdarg.syn]指出:

头文件内容与C标准库头文件<stdarg.h>相同,有以下变化:

  • [...]

这里没有任何限制不允许形成对 的引用std::va_list

C标准中的相关措辞

答案在于C17 标准,7.16 变量参数 <stdarg.h>, p3

声明的类型是

va_list
Run Code Online (Sandbox Code Playgroud)

这是一个完整的对象类型,适合保存宏va_startva_argva_end和所需的信息va_copyap如果需要访问不同的参数,则被调用的函数应声明一个具有类型的对象(通常在本子条款中引用) va_list。该对象ap可以作为参数传递给另一个函数;如果该函数使用va_arg参数调用宏ap,则调用函数中的值是不确定的,并且应在进一步引用 之前ap传递给 宏。257)va_endap

257)允许创建一个指向 a 的指针va_list并将该指针传递给另一个函数,在这种情况下,原始函数可以在另一个函数返回后进一步使用原始列表。

本质上,通过指针传递std::va_list允许被调用者使用它,std::va_list就像我们在自己的函数中使用它一样。由于std::va_list是一个完整的对象类型,因此可以绑定对其的引用。

还值得注意的是,ABI 中的指针和引用是相同的,因此两者都会将内存地址传递给函数,并且行为或多或少相同。


另请参阅:传递 va_list 或指向 va_list 的指针?

在你的情况下通过引用传递va_list是没有意义的

但是,在您的示例中使用引用std::va_list是没有意义的:

void appendFoo(const char * fmt, va_list& rVaList) {
    vsprintf(buffer, fmt, rVaList);
}
Run Code Online (Sandbox Code Playgroud)

问题是它按值std::vsprintf接受 a std::va_list,因此当它va_arg内部调用时,std::va_list调用者中的appendFoo变得不确定。

你也可以按std::va_list价值传递,这不会更糟。

关于 clang-tidy

您可能正在谈论的规则是cppcoreguidelines-pro-type-vararg,它标记了 C 变量的所有使用。

您没有直接调用va_arg,因此并不完全清楚您的使用是否应该被标记。总的来说,这并不奇怪,如果您正在使用 C 变量,您可能希望禁用此检查。

va_list什么时候通过引用或指针传递有意义?

如果您想继续在函数的调用者中使用该列表,这是有意义的:

std::array<int, 3> get_triple(std::va_list& args_ref) {
    return {
        va_arg(args_ref, double), va_arg(args_ref, double), va_arg(args_ref, double)
    };
}

// ...
std::va_list args;
va_start(args, /* ... */);
auto [x, y, z] = get_triple(args);
auto [u, v, w] = get_triple(args); // OK, would be UB if we passed by value
// ...
Run Code Online (Sandbox Code Playgroud)

如果我们没有args_ref通过引用传递,那么使用va_arginget_triple就会变得args不确定。