C中的变量参数,如何获取泛型类型的值?

时间:2009-11-06 17:02:52

标签: c variadic-functions

我正在尝试使用带有泛型类型的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(这个解决方案,在这种情况下不适合好吗?)

感谢所有回答。
抱歉我的英语不好。

6 个答案:

答案 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