Reputation: 151
Upon code review/clang-tidy runs, I came across a function with a signature like this:
void appendFoo(const char * fmt, va_list& rVaList);
I have never seen this before.
Afaik, you can pass va_list
by value (and maybe by pointer?).
This brings me to my first question: Is passing va_list
by reference legal and does it work as expected?
Anyway, appendFoo()
calls vsprintf()
in its definition and clang-tidy gave the following warning: Function 'vsprintf' is called with an uninitialized va_list argument [clang-analyzer-valist.Uninitialized]
. The definition of appendFoo()
look basically like this:
void appendFoo(const char * fmt, va_list& rVaList) {
// retracted: allocate buffer
vsprintf(buffer, fmt, rVaList);
// errorhandling
// de-allocate buffer
}
(Yes, the return value of vsprintf
is ignored and errors are "handled" another way. I'm fixing it ...)
In particular, no va_copy
, va_start
, etc. are called.
Removing the reference and passing va_list
by value, i.e. changing the signature to void appendFoo(const char * fmt, va_list rVaList);
, eliminates the clang-tidy warning.
This brings me to my second question: Is the warning produced by clang-tidy if va_list
is passed by reference a false-positive or is there is actually a problem in passing va_list
as reference?
PS: This question is not a duplicate of varargs(va_list va_start) doesn't work with pass-by-reference parameter. The OP of that question passes an argument by reference to a function taking a va_list. I'm asking about va_list itself being passed as a reference. Oh, well.
Upvotes: 15
Views: 607
Reputation: 39588
Yes, it is allowed. Firstly, [cstdarg.syn] states:
The contents of the header are the same as the C standard library header <stdarg.h>, with the following changes:
- [...]
None of the restrictions here disallow forming a reference to a std::va_list
.
The answer lies in the C17 standard, 7.16 Variable arguments <stdarg.h>, p3:
The type declared is
va_list
which is a complete object type suitable for holding information needed by the macros
va_start
,va_arg
,va_end
, andva_copy
. If access to the varying arguments is desired, the called function shall declare an object (generally referred to asap
in this subclause) having typeva_list
. The objectap
may be passed as an argument to another function; if that function invokes theva_arg
macro with parameterap
, the value ofap
in the calling function is indeterminate and shall be passed to theva_end
macro prior to any further reference toap
.257)257) It is permitted to create a pointer to a
va_list
and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.
Essentially, passing a std::va_list
by pointer allows the callee to use the std::va_list
as if we had used it in our own function.
Since std::va_list
is a complete object type, binding a reference to it is possible.
It's also worth noting that pointers and references are identical in the ABI, so both result in passing a memory address to a function and behave more or less the same.
See also: Pass va_list or pointer to va_list?
va_list
by reference is pointless in your caseHowever, the use of a reference to std::va_list
is pointless in your example:
void appendFoo(const char * fmt, va_list& rVaList) { vsprintf(buffer, fmt, rVaList); }
The issue is that std::vsprintf
accepts a std::va_list
by value, so when it calls va_arg
internally, the std::va_list
in the caller of appendFoo
becomes indeterminate.
You could have just as well passed std::va_list
by value, which would have been no worse.
The rule you're presumably talking about is cppcoreguidelines-pro-type-vararg which flags all uses of C variadics.
You're not directly calling va_arg
, so it's not entirely clear if your use should be flagged. Overall, it's not surprising though, and you probably want to disable this check if you're working with C variadics.
va_list
by reference or pointer?It makes sense if you want to keep using the list in the caller of the function:
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
// ...
If we hadn't passed args_ref
by reference, then the use of va_arg
in get_triple
would have made args
indeterminate.
Upvotes: 2