代码之家  ›  专栏  ›  技术社区  ›  drigoSkalWalker

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

  •  12
  • drigoSkalWalker  · 技术社区  · 15 年前

    我正在尝试使用C stdarg.h lib,它是一个泛型类型。 int类型是我的通用类型,要理解它,请保持阅读。 所以,我的问题是:

    我有一个函数可以接受可变数量的参数。喜欢

    void function (int paramN, ...);

    在我的程序中,没有办法知道变量参数的类型,它可以是char、array、int、short、function point等。喜欢

    function (paramN, "Hey, I'm a string", 1, function_pint, array, -1); // -1 is a sentinel.

    所以,我认为int是32位的,在x86(32位)系统中,它将保存内存的所有地址。所以,如果我用一个int得到所有的参数,这不会是一个问题,例如,“嘿,我是一个字符串”,这个字符串的地址,通常适合32位变量,所以,我只需要进行一个强制转换。

    我说得对吗?
    我能做吗?
    注意:我不想让我的功能像printf(这个解决方案,不适合这个例子,好吗?)

    谢谢你的回答。
    为我糟糕的英语感到抱歉。

    5 回复  |  直到 13 年前
        1
  •  10
  •   kriss    13 年前

    你不能像你描述的那样做。

    C调用约定是让调用者在堆栈上放置参数,但它不放置任何关于类型的信息,因此被调用者必须有方法找到它(至少是变量的大小)。

    • 对于具有原型的函数,每种类型都是已知的。

    • 对于具有变量数或参数(variadic)的函数,更复杂的是,必须为每个参数调用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中定义的stdarg variadic头。要使用它,您需要对函数至少有一个固定参数(在本例中,它是 count )。如果这样,在函数中可以有几个变量列表(例如 myfunc(int count1, ..., int count2, ...) )糟糕的是,你不能有一个纯粹的变量函数(比如myfunc(…)和 古老的 格式。您仍然可以使用varargs兼容头文件的旧格式。但它更复杂,而且很少有必要,因为您需要类型,而且还需要某种方法来知道列表已经完成,计数之类的东西很方便(但不是唯一的方法,例如可以使用“终止符”)。

        2
  •  14
  •   jldupont    15 年前

    为每个参数使用void*(或类型化结构),并使用带有“type”参数(整数)的结构。包含实际值的指针/联合。

    换句话说,每个参数都通过指向类型化结构的指针传递。此类型结构的每个实例都包含一个值。此“值”的类型包含在此类型结构中。

    更好的例子:

    typedef struct  {
      int type;
      union {
        int int_value;
        double double_value;
        ...
      };
    } Param;
    

    void function(Param *p1, Param *p2, ...)

    我遇到的这种技巧的最新例子是dbus。

        3
  •  3
  •   Nathan Kitchen    15 年前

    不能像描述的那样对变量参数执行此操作,因为除非显式执行,否则编译后不会保留有关传递的参数类型的信息。即使传递参数变量的地址也不会告诉您这一点,因为表示字符串的内存中的位可以表示数字或其他东西。

    要使varargs与变量类型一起工作,可以将类型信息存储在参数本身中(例如,如jldupont在其答案中所描述的那样),也可以将信息存储在非变量参数中(例如,格式字符串如 printf s)。

        4
  •  1
  •   dmckee --- ex-moderator kitten    15 年前

    应对 http://en.wikipedia.org/wiki/Stdarg.h :

    没有为确定传递给函数的未命名参数的[…]类型定义机制。函数只是需要知道或确定这一点,其方法是不同的。

    这是你的职责 不能 只从参数中知道哪些参数是字符串。你会 需要 告诉你的函数以某种方式期望什么。这就是为什么 printf 惯例是很普遍的。

        5
  •  0
  •   Tim Schaeffer    15 年前

    试试你?intptr_t,在stdint.h中定义。这是一个整数,保证足够大以容纳指针。

    您可能还想考虑当您传递一个浮点数时会发生什么;它被转换为并作为双精度数传递。如果您的程序需要一个int,这将破坏其堆栈视图。哎哟。编译器无法捕捉到这一点。

    而您的函数,如定义的那样,有(除了在paramn中进行任何位编码,在这种情况下,它应该是一个枚举或位域,或者至少是无符号的)没有办法知道它接收到的参数类型,因此不太可能对它们做任何有用的事情。C的对象没有运行时类型信息。

    你到底想做什么?