对C回调函数的参数有什么好处:va_list或省略号?

时间:2010-10-27 13:43:19

标签: c callback ellipsis variadic-functions

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

typedef void (* callback_func_t)(int type, ...);
void set_callback_func(callback_func_t callback);

在我的库中,我一直在调用这个函数,作为用户设置函数,或者我提供的默认函数。它有效。

现在,我想添加一个间接级别,以便能够调用多个已注册的回调。麻烦的是我的内部回调函数仍然需要省略号参数,也必须用省略号调用回调函数。因此,我的内部函数必须解释type,从va_list解包参数并将它们作为参数提供给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);
}

但是,在用户的回调实现中,根据va_list的值,也会有相同的type用法和参数解释。解决方案是直接将va_list作为参数传递给回调函数,使内部回调函数的实现变得明显。

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

我的问题是:定义一个以va_list为参数的回调函数类型是一种好习惯吗?我可以像上面那样定义我的回调函数类型,但是与顶部的定义相比有哪些优点和缺点?

2 个答案:

答案 0 :(得分:4)

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

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);

本身很短暂。

答案 1 :(得分:3)

我会使用va_list版本。用户已经处理了处理va_list s的可怕性,因此您不会通过隐藏它们来保存任何内容。

此外,使用列表而不是尝试修复参数将减少堆栈使用。如果你必须修复它们的函数的参数,它将创建所有这些参数的新副本,但是传递va_list类型将只使用堆栈中已经存在的那些参数的实例。

最后,您的代码将更简单(如果我正确理解问题),您将保存每个用户不必调用va_startva_end(这可能不会更改在stdarg的大多数实现中输出代码的功能很多,但是否则他们都必须键入这些调用并且(取决于在平台上实现stdarg的方式)他们需要确保实际在返回之前调用va_end(如果发生错误,如果提前返回则容易错过)。