1. Revision History
1.1. Revision 1 - July 22nd, 2022
-
Aligned specification with (now accepted) C23 paper for this feature, [n2975], losing the
specification.parmN
1.2. Revision 0 - February 15th, 2022
-
SG22 saw this paper on February 11th, 2022 and voted in favor of forwarding it towards WG21 and for WG14. They do not see any compatibility issues, but recommended two things:
-
Allowing declarations but not definitions in the C paper;
-
and, talking about ABI changes.
-
-
A section
-
Initial Release! 🎉
2. Introduction and Motivation
C merged a paper to remove functions without prototypes ("K&R Functions") from C23, closing the chapter on a nearly 40 year old deprecated feature. Unfortunately, K&R declarations served a number of useful purposes that could not be effectively approximated by any other function declaration in C. In C++, old-style K&R functions on both an ABI and an API level could be approximated with a declaration such as
. This was impossible in C thanks to the requirement in both the language that there must be at least one parameter before the
, and that the last parameter must be passed to
. This is bad because many inter-language function calls and similar were enabled in plain, standard C by using K&R declarations and then having the non-C side use assembly or other tricks to appropriately handle both passed-in arguments as well as return values:
// Pre-C23 K&R Declaration double compute_values (); int main () { // C: allowed usage under K&R rules, removed in C23 // C++: ill-formed (constraint violation), function takes 0 arguments double x = compute_values ( "with_pi" , 2.4 , 2.7 , true); return ( int ) x ; }
The implementation of
here could be done VIA assembly or with other tricks in other languages, allowing a C codebase to talk to other programming languages and tools efficiently without having to create a dedicated Foreign Function Interface. Unfortunately, the removal of K&R declarations in C has made the above code illegal in standard C, and taken away a fairly valid use case for prototype-less functions.
2.1. The Solution
C is moving to fix this problem by allowing
as a valid function declaration, as shown in [n2919]. This will allow the same level of power as K&R declarations without the problematic no-arguments-but-takes-any-number-of-arguments-actually double-meaning syntax. In order to do this, C is updating the
macro as well as fixing its language rules. C++ does not need to fix any of its language rules:
// Valid in C23, always valid C++ double compute_values (...); int main () { // C and C++: allowed and portable in both languages double x = compute_values ( "with_pi" , 2.4 , 2.7 , true); return ( int ) x ; }
Since C++ includes the
header as part of its library offerings in
, some adjustments must be made to the contents of the synopsis and wording for
to match the changes that will be made for C. Additionally, some modifications must be made to the constraints to match the changes C makes. Because this is something that was not previously allowed before, it has no impact on existing implementations and for all major compilers (GCC, MSVC, Clang, and their derivates) they have the necessary built-in compiler magic to produce working library implementations that do not require the first argument to
.
An example proving that this is possible is publicly available here: ztd.vargs (https://ztdvargs.readthedocs.io/en/latest/). The ABI for variadic arguments versus K&R prototypes is no affected because the C ABI did not allow this declaration before, so there is no existing standard code for C that could rely on this function call. C++ may have an ABI for it, but no standards-compliant code could access any of the function arguments using
/
/
thanks to the macro’s specification. Therefore, this feature either introduces a new ABI that did not previously exist on the platform at all, or simply utilizes an existing ABI (the example library implementation leverages well-defined existing ABIs in C++ implementations in order to work properly).
2.1.1. What if the ABI is different?
There are a handful of C compilers that allow declarations using variable arguments without a first parameter, and some shared C and C++ compiler implementations which also allow it by having
functions declared without the first parameter in C++. Therefore, there can be compilers which do not share an ABI for their K&R and their variable argument declarations.
The solution here is not something that can be specified in the standard. This paper can only recommend that implementations which need more explicit control over the resulting ABI of their K&R functions may need to provide an attribute. like below, when performing the migration:
[[ impl :: krdecl ]] double compute_values (...); int main () { // compute_values uses right register and stack allocation convention double x = compute_values ( "with_pi" , 2.4 , 2.7 , true); return ( int ) x ; }
This can alleviate much of the trouble of porting, and can still be automated when upgrading to C23. We do not have any means in the standard to provide an
or similar because ABI, register usage, and similar calling convention work is somewhat outside the scope of the standard.
2.1.2. Allow Declaration, not but don’t allow Definitions?
There was discussion about allowing this to change only the ability in C to declare these C functions, and not to define them. This is different from C++ where it is both allowed to be declared and defined with no first parameter. Clang also has an extension that allows these functions to be both declared and defined with C or C++-style name mangling:
void func (...) __attribute__ (( overloadable )) { } int main () { func ( 1 , 2 , 3 ); }
The original paper in [n2975] and this paper posit that it would be too inconsistent to allow declaratins (like C++) but disallow definitions (unlike C++) in C. Therefore, the C paper is going forward with changing
and allowing both declarations and definitions. This paper is also being put in the C++ mailing list to bring C++ up to the same level of compatibility with C, if the N2919 change is made. If the change is withdrawn than this paper will be removed.
3. Specification
The specification is relative to the latest C++ Working Draft, [n4901].
3.1. Library Wording
3.1.1. Modify Header Synopsis [cstdarg.syn]
namespace std { using va_list = see below ; } #define va_arg(V, P) see below #define va_copy(VDST, VSRC) see below #define va_end(V) see below #define va_start(V, P) see below #define va_start(V, ...) see below } The contents of the header
are the same as the C standard library header
< cstdarg > , with the following changes: The restrictions that ISO C places on the second parameter , if provided, to the
< stdarg . h > macro in header
va_ start are different in this document. The second parameter to
< stdarg . h > , if provided,
va_start parameteris the rightmost parameter in the variable parameter list of the function definition (the one just before the
parmN )207. If the provided parameter
... is a pack expansion ([temp.variadic]) or an entity resulting from a lambda capture ([expr.prim.lambda]), the program is ill-formed, no diagnostic required. If the provided 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.
parmN SEE ALSO: ISO C 7.16.1.1