Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: minor editorial changes

...

These restrictions are superseded by the C++ Standard, [support.runtime], paragraph 3 [ISO/IEC 14882-2014], which states the following:

The restrictions that ISO C places on the second parameter to the va_start() macro in header <stdarg.h> are different in this International Standard. The parameter parmN is the identifier of the rightmost parameter in the variable parameter list of the function definition (the one just before the ...). If the parameter parmN is of a reference type, or of a type that is not compatible with the type that results when passing an argument for which there is no parameter, the behavior is undefined.

...

  • You must not pass a reference as the second argument to va_start().
  • Passing an object of a class type which that has a nontrivial copy constructor, nontrivial move constructor, or nontrivial destructor as the second argument to va_start is conditionally supported with implementation-defined semantics ([expr.call] paragraph 7).
  • You may pass a parameter declared with the register keyword ([dcl.stc] paragraph 3) or a parameter with a function type.

Passing an object of array type still produces undefined behavior in C++ because an array type as a function parameter requires use the use of a reference, which is prohibited. Additionally, passing an object of a type that undergoes default argument promotions still produces undefined behavior in C++ as well.

Noncompliant Code Example

In this noncompliant code example, the object passed to va_start() will undergo a default argument promotion, which results in undefined behavior:.

Code Block
bgColor#FFcccc
langcpp
#include <cstdarg>
 
extern "C" void f(float a, ...) {
  va_list list;
  va_start(list, a);
  // ...
  va_end(list);
}

...

In this compliant solution, f() accepts a double instead of a float:.

Code Block
bgColor#ccccff
langcpp
#include <cstdarg>
 
extern "C" void f(double a, ...) {
  va_list list;
  va_start(list, a);
  // ...
  va_end(list);
}

...

In this noncompliant code example, a reference type is passed as the second argument to va_start():.

Code Block
bgColor#FFcccc
langcpp
#include <cstdarg>
#include <iostream>
 
extern "C" void f(int &a, ...) {
  va_list list;
  va_start(list, a);
  if (a) {
    std::cout << a << ", " << va_arg(list, int);
    a = 100; // Assign something to a for the caller
  }
  va_end(list);
}

...

Instead of passing a reference type to f(), this compliant solution passes a pointer type:.

Code Block
bgColor#ccccff
langcpp
#include <cstdarg>
#include <iostream>
 
extern "C" void f(int *a, ...) {
  va_list list;
  va_start(list, a);
  if (a && *a) {
    std::cout << a << ", " << va_arg(list, int);
    *a = 100; // Assign something to *a for the caller
  }
  va_end(list);
}

...

In this noncompliant code example, a class with a nontrivial copy constructor (std::string) is passed as the second argument to va_start(), which is conditionally supported depending on the implementation:.

Code Block
bgColor#FFcccc
langcpp
#include <cstdarg>
#include <iostream>
#include <string>
 
extern "C" void f(std::string s, ...) {
  va_list list;
  va_start(list, s);
  std::cout << s << ", " << va_arg(list, int);
  va_end(list);
}

...

This compliant solution passes a const char * instead of a std::string, which has well-defined behavior on all implementations:.

Code Block
bgColor#ccccff
langcpp
#include <cstdarg>
#include <iostream>
 
extern "C" void f(const char *s, ...) {
  va_list list;
  va_start(list, s);
  std::cout << (s ? s : "") << ", " << va_arg(list, int);
  va_end(list);
}

...