【问题标题】:Variable arguments in C, how to get values with a generic type?C中的变量参数,如何获取泛型类型的值?
【发布时间】:2009-11-06 17:02:51
【问题描述】:

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

感谢所有回答。
对不起,我的英语不好。

【问题讨论】:

    标签: c variadic-functions


    【解决方案1】:

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

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

    更好的例子:

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

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

    我遇到的这种技巧的最新示例是 DBus。

    【讨论】:

    • 你能更好地解释一下吗?我想我理解了,例如,我可以用可能的类型 { INT, SHORT, CHAR, ARRAY...ETC } 制作一个枚举,然后,我将制作一个包含两个字段的结构,一个字段的类型为枚举对吗?另一个有价值吗?这是正确的?如何保值?对不起,如果我错了,谢谢你的回答。
    【解决方案2】:

    你不能按照你描述的方式去做。

    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 标准可变参数标头。要使用它,您的函数至少需要一个固定参数(在此示例中为count)。如果这样你就可以在你的函数中有几个可变参数列表(例如myfunc(int count1, ..., int count2, ...)),这是一件好事。坏事是你不能有一个纯粹的可变参数函数(即像 myfunc(...) 这样的 old 格式。你仍然可以使用旧格式使用可变参数兼容性标头。但是它更复杂且很少需要,因为您需要类型,但还需要某种方法来知道列表已完成,并且 count 之类的东西很方便(但不是唯一的方法,例如可以使用“终止符”)。

    【讨论】:

    • 注意:我不想让我的函数像 printf (这个解决方案,不适合这种情况好吗?)
    • 哇,谢谢kriss,您是专家,感谢您的宝贵时间,答案很棒!非常感谢;D
    【解决方案3】:

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

    要使可变参数与可变类型一起使用,您可以将类型信息存储在参数本身中(例如,如 jldupont 在他的回答中所描述的那样),或者您可以将信息存储在非可变参数中(例如,格式字符串比如printf's)。

    【讨论】:

    • 注意:我不想让我的函数像 printf (这个解决方案,不适合这种情况好吗?)
    • 我明白了。我只是想弄清楚为什么其他人会选择您拒绝的解决方案。
    • 好的,内森,谢谢你的回答,我只想用其他方法来完成这个好吗?无论如何,谢谢你的回答家伙! ;D
    【解决方案4】:

    来自http://en.wikipedia.org/wiki/Stdarg.h

    没有定义机制来确定传递给函数的未命名参数的 [...] 类型。该函数只需要以某种方式知道或确定这一点,其方式各不相同。

    那是你的函数不能仅仅从参数中知道哪些参数是字符串。您将需要以某种方式告诉您的函数期望什么。这就是printf 约定如此普遍的原因。

    【讨论】:

    • 注意:我不想让我的函数像 printf (这个解决方案,不适合这种情况好吗?)
    • 我们明白这一点。 没有简单的方法可以完成您的要求。您必须找到一种方法来告诉可变参数函数参数中的数据类型。 printf 约定不是唯一的选择(请参阅链接或 jldupont 的答案),但你总是不得不说。
    • 好的dmckee,谢谢你的回答,我只想用其他方法来完成这个好吗?无论如何,谢谢你的回答家伙! ;D
    【解决方案5】:

    扩展krissidea

    自 C99 起,使用 _Generic 将参数对传递给可变参数函数。

    更改调用以使用宏G(obj)

    // test("Hello", "world", 15, 16.000);
    test(G("Hello"), G("world"), G(15), G(16.000), 0);
    

    G 是一个宏,它使用_Generic 来区分类型并提供枚举器。然后传递给test 的两个参数:一个用于标识类型的枚举器,然后是对象本身。

    这迫使_Generic 枚举许多类型。可能的类型有无数种,但一个基本的列表_Bool, char, long long, double, char *等是合理的。

    然后test() 使用枚举来引导选择下一个参数。推荐0 结束列表。


    一个更完整的例子:
    Formatted print without the need to specify type matching specifiers using _Generic

    【讨论】:

    【解决方案6】:

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

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

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

    你到底想做什么?

    【讨论】:

    • 嗨,伙计,你的回答很好,我正在尝试将一些函数点传递给我的可变参数函数,但在它们之间,我也会传递一个字符串或一个 int,我认为我使用 int 来完成这个没有问题,或者,也使用 intptr_t,你告诉我什么?谢谢你的回答o/
    • 你的程序必须知道每个参数的类型,而在你的函数中知道这一点的唯一方法是通过你的参数以某种方式传递这些信息。如果你调用 printf 像 printf("%s:%d","string",i) 它使用它的第一个参数作为一个字符串,第二个作为一个 int 因为格式字符串告诉它这样做。如果用户传递了其他东西,比如 printf("%s:%d",0,"x") printf 不能知道,那么你的程序就出错了。你如何告诉你的函数如何使用它的参数?它怎么知道哪些是整数,哪些是字符串,哪些是指针,&c?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-15
    • 1970-01-01
    相关资源
    最近更新 更多