Reputation: 2016
I want to call an extern "C"
function e.g. f1(int a, float f, double d, void* ptr)
using an forward declaration with actual parameters but in the actual implementation I would like to to use va_list
and friends to pop
the args. Let's imagine for a second that I have a valid usecase for it, is it allowed?
main.cpp
extern "C" void f(int anchor, int a, float f, double d, void* ptr, char c);
int main(int, char*)
{
f(0, 42, 1.08f, 3.14, reinterpret_cast<void*>(0xcafebabe), 'c');
return 0;
}
impl.cpp
#include <cstdarg>
#include <iostream>
#include <iomanip>
using namespace std;
void f(int anchor, ...)
{
va_list args;
va_start(args, anchor);
int a = va_arg(args, int);
float f = va_arg(args, float);
double d = va_arg(args, double);
void* ptr = va_arg(args, void*);
char c = va_arg(args, char);
cout << a << ' '
<< f << ' '
<< d << ' '
<< hex << (std::ptrdiff_t)ptr << ' '
<< (int)c << endl;
va_end(args);
}
The code runs and prints the correct values on MSVC 2015 atleast, the question now is: is it guaranteed to work, if not: will it probably work on the most important platforms and compilers anyway?
Upvotes: 0
Views: 167
Reputation: 81247
Some platforms require that every function must receive a fixed set of arguments. A C compiler for such a platform would likely handle something like:
printf("%d %f %s", 9, 23.7, "Fred");
with code equivalent to:
union long_or_double_or_ptr { unsigned long l; double d; void *p; };
union long_or_double_or_ptr INTERNAL_ARGS_92512[3];
__INTERNAL_ARGS_92512[0].l = 9;
__INTERNAL_ARGS_92512[1].d = 23.7;
__INTERNAL_ARGS_92512[2].p = "Fred";
printf("%d %f %s", INTERNAL_ARGS_92512);
The printf function would, from the standpoint of the machine running it, always accept two parameters: a "const restrict char *" and a "union long_or_double_or_ptr[]". The intrinsics defined in stdarg.h would know how to fetch data from that type, thus allowing the program to use it with no particular difficulty.
On such a platform, the calling convention for a variadic method would thus look nothing like the calling convention for a method accepting multiple discrete arguments, and no compatibility with such should be expected.
Note that your method had been written as:
void f(anchor, a, f, d, ptr, c)
int anchor;
int a;
float f;
double d;
void* ptr;
char c;
{ /* Note parameters go before the brace! */
/* code goes here */
}
and not declared with a prototype, that might more likely get handled the same way as the variable-arguments scenario (since old C allowed callers to omit arguments if the called function never read them, and in the absence of a prototype a compiler would have no way of knowing how many arguments the function would need) but that syntax has been deprecated for decades now and I don't know how many compilers would even accept it.
Upvotes: 0
Reputation: 206707
Your code is, in theory, subject to undefined behavior. In fact, when I tried running the program after building with g++, I got
Illegal instruction (core dumped)
The reason is that when a function has the variable argument parameter, the only things it can deal with are int
s, double
s, pointers. It can't deal with float
s and char
s.
Had the declaration been specified as:
extern "C" void f(int anchor, ...);
the float
would be promoted to a double
and the char
would be promoted to an int
.
On the implementation side, you'd need to use:
float f = (float)va_arg(args, double);
and
char c = (char)va_arg(args, int);
See Default argument promotions in C function calls for more info on the subject.
Upvotes: 2