函数参数中的省略号是否使用与普通参数相同的调用布局
Do ellipses in an function parameter use the same call layout as normal parameters
我想调用一个 extern "C"
函数,例如f1(int a, float f, double d, void* ptr)
使用带有实际参数的前向声明,但在实际实现中我想使用 va_list
和朋友 pop
args。让我们想象一下,我有一个有效的用例,它被允许吗?
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);
}
代码至少在 MSVC 2015 上运行并打印正确的值,现在的问题是:它是否保证可以工作,如果不能:它是否可能在最重要的平台和编译器上工作?
从理论上讲,您的代码受未定义行为的影响。事实上,当我在用 g++ 构建后尝试 运行 程序时,我得到了
Illegal instruction (core dumped)
原因是当一个函数有可变参数时,它能处理的只有int
s,double
s,指针。它无法处理 float
s 和 char
s.
声明是否指定为:
extern "C" void f(int anchor, ...);
float
将晋升为 double
,char
将晋升为 int
。
在实施方面,您需要使用:
float f = (float)va_arg(args, double);
和
char c = (char)va_arg(args, int);
有关该主题的更多信息,请参阅 Default argument promotions in C function calls。
一些平台要求每个函数必须接收一组固定的
争论。这种平台的 C 编译器可能会处理一些事情
喜欢:
printf("%d %f %s", 9, 23.7, "Fred");
代码等同于:
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);
从机器的角度来看,printf 函数 运行 它,
始终接受两个参数:a "const restrict char *" 和 "union
long_or_double_or_ptr[]”。stdarg.h 中定义的内在函数会知道
如何从该类型中获取数据,从而允许程序将其与
没有特别困难。
在这样的平台上,可变参数方法的调用约定将因此
看起来一点都不像接受多个方法的调用约定
离散参数,并且不应该与此类兼容。
请注意,您的方法已写为:
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 */
}
并且没有用原型声明,这更有可能得到处理
与变量参数场景相同的方式(因为旧 C 允许调用者
如果被调用的函数从不读取参数,则省略参数,并且在
没有原型,编译器将无法知道有多少
函数需要的参数)但该语法已被弃用
几十年了,我不知道有多少编译器会接受它。
我想调用一个 extern "C"
函数,例如f1(int a, float f, double d, void* ptr)
使用带有实际参数的前向声明,但在实际实现中我想使用 va_list
和朋友 pop
args。让我们想象一下,我有一个有效的用例,它被允许吗?
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);
}
代码至少在 MSVC 2015 上运行并打印正确的值,现在的问题是:它是否保证可以工作,如果不能:它是否可能在最重要的平台和编译器上工作?
从理论上讲,您的代码受未定义行为的影响。事实上,当我在用 g++ 构建后尝试 运行 程序时,我得到了
Illegal instruction (core dumped)
原因是当一个函数有可变参数时,它能处理的只有int
s,double
s,指针。它无法处理 float
s 和 char
s.
声明是否指定为:
extern "C" void f(int anchor, ...);
float
将晋升为 double
,char
将晋升为 int
。
在实施方面,您需要使用:
float f = (float)va_arg(args, double);
和
char c = (char)va_arg(args, int);
有关该主题的更多信息,请参阅 Default argument promotions in C function calls。
一些平台要求每个函数必须接收一组固定的 争论。这种平台的 C 编译器可能会处理一些事情 喜欢:
printf("%d %f %s", 9, 23.7, "Fred");
代码等同于:
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);
从机器的角度来看,printf 函数 运行 它, 始终接受两个参数:a "const restrict char *" 和 "union long_or_double_or_ptr[]”。stdarg.h 中定义的内在函数会知道 如何从该类型中获取数据,从而允许程序将其与 没有特别困难。
在这样的平台上,可变参数方法的调用约定将因此 看起来一点都不像接受多个方法的调用约定 离散参数,并且不应该与此类兼容。
请注意,您的方法已写为:
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 */
}
并且没有用原型声明,这更有可能得到处理 与变量参数场景相同的方式(因为旧 C 允许调用者 如果被调用的函数从不读取参数,则省略参数,并且在 没有原型,编译器将无法知道有多少 函数需要的参数)但该语法已被弃用 几十年了,我不知道有多少编译器会接受它。