13

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

谢谢大家的回答。
对不起我的英语不好。

4

6 回答 6

14

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

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

更好的例子:

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

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

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

于 2009-11-06T17:07:56.817 回答
12

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

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

于 2009-11-06T17:51:28.403 回答
3

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

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

于 2009-11-06T17:19:30.263 回答
1

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

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

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

于 2009-11-06T17:23:18.367 回答
1

扩展克里斯的想法

自 C99 起,用于_Generic向可变参数函数传递参数对。

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

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

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

_Generic需要列举许多类型。有无数种可能的类型,但基本列表_Bool, char, long long, double, char *等是合理的。

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


更完整的示例:
格式化打印,无需使用 _Generic 指定类型匹配说明符

于 2020-01-25T19:08:13.883 回答
0

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

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

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

你到底想做什么?

于 2009-11-06T17:29:41.147 回答