对于C回调函数的参数,什么更好:va_列表,还是省略号?

对于C回调函数的参数,什么更好:va_列表,还是省略号?,c,callback,ellipsis,variadic-functions,C,Callback,Ellipsis,Variadic Functions,我的库提供了一个回调点,我的库用户可以在这里注册以获取信息。回调的一般形式是int,后跟各种参数,这些参数的类型取决于int值。因此,我定义了回调类型和设置回调类型的函数,如下所示 typedef void (* callback_func_t)(int type, ...); void set_callback_func(callback_func_t callback); 在我的库中,我一直在调用这个函数,作为用户集函数,或者是我提供的默认函数。它起作用了 现在,我想添加一个间接层,以便能

我的库提供了一个回调点,我的库用户可以在这里注册以获取信息。回调的一般形式是
int
,后跟各种参数,这些参数的类型取决于int值。因此,我定义了回调类型和设置回调类型的函数,如下所示

typedef void (* callback_func_t)(int type, ...);
void set_callback_func(callback_func_t callback);
在我的库中,我一直在调用这个函数,作为用户集函数,或者是我提供的默认函数。它起作用了

现在,我想添加一个间接层,以便能够调用几个已注册的回调。问题是,我的内部回调函数仍然使用省略号参数,还必须使用省略号调用回调函数。因此,我的内部函数必须解释
类型
,从
va_列表
中解压缩参数,并将其作为参数提供给callbacj函数

void internal_callback(int type, ...) {
    va_list args;
    va_start(args, type);
    switch (type) {
    case 0: call_callback(type, va_arg(args, int)); break;
    // ...
    }
    va_end(args);
}
但是,在用户的回调实现中,根据
type
的值,还会有相同的
va_列表
用法和参数解释。解决方案是直接将
va_list
作为参数传递给回调函数,使内部回调函数的实现变得显而易见

typedef void (* callback_func_t)(int type, va_list args);

我的问题是:定义一个以
va_list
为参数的回调函数类型是一种好的做法吗?我可以如上所述定义回调函数类型,但与顶部的定义相比,它的优缺点是什么?

我假设类型的数量有限且已知?如果是这样,为什么不使用枚举和联合来实现某种级别的类型安全性,顺便说一句,这也可以解决您的问题:

enum callback_arg_type { INT_ARG, DOUBLE_ARG, VECTOR_ARG };

union callback_arg
{
    int as_int;
    double as_double;
    struct { double x, y, z; } as_vector;
};

typedef void (*callback_func_t)(
    enum callback_arg_type type, union callback_arg arg);
根据参数大小的不同,最好传递一个指针。您还可以提供一些宏来为回调调用提供更好的语法,但如果它们仅从库中调用,则可能不值得:

union callback_arg arg = { .as_int = 42 };
callback_fn(INT_ARG, arg);

它本身就很短。

我会选择
va_列表
版本。用户已经在处理
va_list
s带来的恐惧,因此您不会通过向他们隐藏来保存任何内容

此外,使用列表而不是尝试重新通过参数将减少堆栈的使用。如果必须将这些参数重新传递给它们的函数,它将为所有这些参数创建一个新的副本,但是传递
va_list
类型将只使用堆栈上已经存在的这些参数的实例

最后,您的代码将更加简单(如果我正确理解了这个问题),并且您将使每个用户不必调用
va_start
va_end
(对于大多数
stdarg
的实现,这可能不会对其函数中的输出代码造成太大的更改)但除此之外,他们都必须键入这些调用(取决于
stdarg
s在平台上的实现方式),他们需要确保在返回之前确实调用了
va_end
(如果出现错误,提前返回很容易出错)