我正在尝试使用带有泛型类型的C stdarg.h lib。 int类型是我的泛型类型>要理解它,请继续阅读。 所以,我的问题是:
我有一个接受可变数量参数的函数。像
void function (int paramN, ...);
在我的程序中,没有办法知道,哪个是变量参数的类型,它可以是char,数组,int,short,函数点等...比如
function (paramN, "Hey, I'm a string", 1, function_pint, array, -1); // -1 is a sentinel.
所以,我认为,一个int,是32位,在x86(32位)系统中,这将保存所有内存地址。所以,如果我用int得到所有参数,那就不会有问题了,例如,“嘿,我是一个字符串”这个字符串的地址,通常适合32位变量,所以,我只需要制作演员。
我是对的?
我可以这样做吗?
注意:我不想让我的函数像printf(这个解决方案,在这种情况下不适合好吗?)
感谢所有回答。
抱歉我的英语不好。
答案 0 :(得分:14)
对每个参数使用void *(或类型化结构)&使用带有“type”参数的结构(整数)。用于包含实际值的指针/联合。
换句话说,每个参数都传递一个指向类型化结构的指针。此类型结构的每个实例都包含一个值。此“值”的类型包含在此类型的结构中。
更好的例子:
typedef struct {
int type;
union {
int int_value;
double double_value;
...
};
} Param;
void function(Param *p1, Param *p2, ...)
我遇到的这种技巧的最新例子是DBus。
答案 1 :(得分:10)
你不能按照描述的方式去做。
C调用约定是让调用者将参数放在堆栈上,但它不会在类型上放置任何信息,因此被调用者必须有办法找到它(至少是变量的大小)。
对于每种类型的原型函数都没有问题。
对于具有可变数字或参数(可变参数)的函数,它更棘手,您必须为每个参数调用va_arg来读取每个变量,并且必须提供va_arg的类型。如果你提供的类型不是真正的类型,编译器就不会抱怨(它不能是运行时信息),但任何事情都可能发生(通常是坏事)。
因此你必须传递类型。
在某些情况下,您可以预测类型(例如:计数器后跟一些整数,依此类推),但通常会将其传递给参数编码。你可以用像printf那样的格式字符串传递它,jldupont描述的联合技巧也很常见。
但无论如何你必须通过它。
你真的不能依赖底层数据二进制表示,甚至数据大小。即使程序在编写时似乎有效,它也没有兼容性,并且可以在系统,编译器或更改编译选项时更改。
让我们来看一个示例,其中传递类型后跟参数(因此既不是联合技巧也不是像printf那样的格式字符串)。它的作用是将所有传递的值转换为double并添加它们,没有真正有用的不是它:
#include <stdio.h>
#include <stdarg.h>
enum mytypes {LONG, INT, FLOAT, DOUBLE };
double myfunc(int count, ...){
long tmp_l;
int tmp_i;
double tmp_d;
double res = 0;
int i;
va_list ap;
va_start(ap, count);
for(i=0 ; i < count; i++){
int type = va_arg(ap, enum mytypes);
switch (type){
case LONG:
tmp_l = va_arg(ap, long);
res += tmp_l;
break;
case INT:
tmp_i = va_arg(ap, int);
res += tmp_i;
break;
case FLOAT:
/* float is automatically promoted to double when passed to va_arg */
case DOUBLE:
tmp_d = va_arg(ap, double);
res += tmp_d;
break;
default: /* unknown type */
break;
}
}
va_end(ap);
return res;
}
int main(){
double res;
res = myfunc(5,
LONG, (long)1,
INT, (int)10,
DOUBLE, (double)2.5,
DOUBLE, (double)0.1,
FLOAT, (float)0.3);
printf("res = %f\n", res);
}
此示例使用C99中定义的 new stdarg可变参数头。要使用它,你需要为你的函数至少有一个固定的参数(在这个例子中它是count
)。好的,如果这样你可以在你的函数中有几个可变参数列表(例如像myfunc(int count1, ..., int count2, ...)
)。不好的是你不能拥有纯粹的变量函数(比如myfunc(...)就像旧格式一样。你仍然可以使用varargs兼容性头文件来使用旧格式。但是它更复杂,而且很少需要,因为你需要类型,但也有一些方法可以知道列表已经完成,而像count这样的东西很方便(但不是唯一的方法,例如,可以使用'终止符')。 / p>
答案 2 :(得分:3)
您不能以您描述的方式使用变量参数执行此操作,因为除非您明确地执行此操作,否则在编译后不会保留有关所传递的参数类型的信息。即使传递参数变量的地址也不会告诉你这一点,因为表示字符串的内存中的位可以表示数字或其他内容。
要使varargs使用变量类型,您可以在参数本身中存储类型信息(例如,如jldupont在其答案中所述),或者您可以将信息存储在非变量参数中(例如,格式字符串)比如printf
')。
答案 3 :(得分:1)
来自http://en.wikipedia.org/wiki/Stdarg.h:
没有定义机制来确定传递给函数的未命名参数的类型。该函数只需要以某种方式知道或确定它,其方法各不相同。
这是你的函数不能只知道参数是字符串的参数。您将需要告诉您的功能以某种方式期待什么。这就是printf
惯例如此普遍的原因。
答案 4 :(得分:0)
尝试使用u?intptr_t,它在stdint.h中定义。这是一个整数,保证大到足以容纳指针。
您可能还想考虑传递浮点数时会发生什么;它被转换为double并作为double传递。如果你的程序期望一个int,这将破坏它的堆栈视图。哎哟。并且编译器无法捕捉到这一点。
你的函数,如定义的那样,(禁止paramN中的任何位编码,在这种情况下它应该是枚举或位域,或者至少是无符号)无法知道它接收的参数类型是什么,因此不太可能对它们做任何有用的事情。 C没有关于其对象的运行时类型信息。
你到底想要做什么?
答案 5 :(得分:0)
扩大kriss的想法
从C99开始,使用!==
传递给可变参数函数对参数。
将呼叫更改为使用宏_Generic
G(obj)
// test("Hello", "world", 15, 16.000);
test(G("Hello"), G("world"), G(15), G(16.000), 0);
是一个宏,它使用G
来区分类型并提供枚举器。然后传递两个_Generic
的参数:一个枚举器,其类型是ID,然后是对象本身。
这迫使test
枚举许多类型。可能的类型有无数,但是基本列表_Generic
等是合理的。
然后_Bool, char, long long, double, char *
使用枚举引导对下一个参数的选择。建议使用test()
结束列表。
更完整的示例:
Formatted print without the need to specify type matching specifiers using _Generic