C 回调函数的参数哪个更好:va_list 或省略号?

发布于 2024-09-29 13:13:01 字数 990 浏览 1 评论 0原文

我的图书馆提供了一个回调点,我的图书馆用户可以在其中注册以获取信息。回调的一般形式是一个 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 作为参数的回调函数类型是一个好的做法吗?我可以像上面那样定义我的回调函数类型,但是与顶部的定义相比有什么优点和缺点?

My library offers a callback point where my library users can register to get information. The general form of the callback is an int followed by various parameters whose type depend on the int value. Therefore, I defined the callback type and the function to set it as follows.

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

Within my library, I am calling this function all throughout, being the user set function, or the default one I am providing. It works.

Now, I'd like to add a level of indirection, to be able to call several registered callbacks. The trouble is that my internal callback function that still takes ellipsis parameters, also have to call the callback function with ellipsis. As a consequence, my internal function has to interpret the type, unpack the parameters from the va_list and give them as parameters to the callbacj function.

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

But then, in the user's implementation of the callback, there will also be the same va_list usage, and interpretation of the arguments, according to the value of type. The solution is to pass directly the va_list as an argument to the callback function, making the implementation of the internal callback function obvious.

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

My question is: is it good practice to define a callback function type that takes va_list as argument? I can define my callback function type as above, but what are the pros and cons compared to the definition at the top?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

魔法少女 2024-10-06 13:13:01

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

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

它本身就很短。

I assume there is a finite and known number of types? If so, why not use enumerations and unions for some level of type safety, which would incidentally also solve your problem:

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

Depending on the difference of argument sizes, it might be a good idea to pass a pointer instead. You could also provide some macros to provide a nicer syntax for callback invocation, but if they are only ever called from within your library, it might not be worth it:

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

is pretty short on its own.

┈┾☆殇 2024-10-06 13:13:01

我会选择 va_list 版本。用户已经在处理处理 va_list 的可怕问题,因此您不会通过向他们隐藏来保存任何内容。

另外,使用列表而不是尝试重新传递参数将减少堆栈的使用。如果您必须将参数重新传递给其函数,它将创建所有这些参数的新副本,但传递 va_list 类型将仅使用堆栈上已有的这些参数的实例。

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

I would go with the va_list version. The user is already dealing with the scariness of dealing with va_lists so you don't save anything by hiding that from them.

Additionally using the list rather than trying to repass the arguments will cut down on stack usage. If you had to repass the arguments to their function it would make a new copy of all of those arguments, but passing the va_list type will just use the instance of those arguments already on the stack.

Finally, your code will both be simpler (if I'm understanding the problem correctly) and you will save each user from having to call va_start and va_end (which probably won't change the output code in their function much for most implementations of stdarg) but otherwise they all have to type those calls and (depending on how stdargs are implemented on the platform) they will need to make sure they actually do call va_end before returning (easy to miss if returning early in case of an error).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文