使用 char[] 获取可变大小的 C 可变参数

Obtaining C varargs with variable size using char[]

我正在 C 中编写一个容器库,我想在我的实现中使用可变参数,如下所示:

void stack_push(stack *self, T item);
T stack_pop(stack *self);

显然,C 没有泛型类型,所以我使用 void * 指针:

void stack_push(stack *self, void *item);
void *stack_pop(stack *self);

但是,我正在考虑使用可变参数传递输入:

void stack_push(stack *self, ...);

这可行,因为项目大小是在容器初始化时确定的。

我的问题是:使用具有相同大小的不同类型访问可变参数成员是否有效C

void stack_push(stack *self, ...)
{
    struct wrapper {
        char item[self->item_size];
    };

    va_list ap;
    struct wrapper item;

    va_start(ap, self);
    item = va_arg(ap, struct wrapper);
    va_end(ap);

    stack_grow(self, self->size+1);
    memcpy(self->items+self->item_size*self->size++, &item, self->item_size);
}

按您的意愿行事会引发未定义的行为。

来自 C 标准(C11 草案):

7.16.1.1 The va_arg macro

Synopsis

1

#include <stdarg.h>
type va_arg(va_list ap, type);

Description

2 [...] if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases:

— one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types;

— one type is pointer to void and the other is a pointer to a character type.

None 两个异常似乎符合您的用例。

进一步回答我自己的问题:不,不可能将可变参数作为 char[]sizeof type:

  • type in va_arg(ap, type); 不能是数组。
  • type 可以是 struct,但 struct 不能有 VLA。
  • 对于 GCC,它期望 VLA structs 作为指针传递。
  • 多次调用 va_arg(ap, char); 会导致未定义的行为。 va_arg 调用的数量必须等于参数的数量。

您能做的最好的事情就是为每个可能的项目大小定义一个类型,然后使用它加上一个 switch 语句。

struct s1 { char x[1]; };
struct s2 { char x[2]; };
...

void stack_push(stack *s, ...)
{
    va_list ap;
    union {
        struct s1 s1;
        struct s2 s2;
        ...
    } u;

    va_start(ap, s);
    switch (s->item_size)
    {
    case 1:
        u.s1 = va_arg(ap, struct s1);
        break;
    case 2:
        u.s2 = va_arg(ap, struct s2);
        break;
    ...
    }
    va_end(s);

    stack_grow(s, self->size+1);
    memcpy(self->items + self->item_size*self->size++, &u, self->item_size);
}

但是,对于任何试图实现类似机制(即传递文字而不是指针)的人,我建议如下:

#include <stack>

#define stack(T) struct {stack actual; T item;}
#define stack_init(s) (stack_init)(&(s)->actual, sizeof((s)->item))
#define stack_push(s, i) ((s)->item = (i); (stack_push)(&(s)->actual, &(s)->item))
#define stack_pop(s) (memset(&(s)->item, 0, sizeof((s)->item)), (stack_pop)(&(s)->actual, &(s)->item), (s)->item)

int main(void)
{
    stack(int) s;

    stack_init(&s);
    stack_push(&s, 3);
    printf("%d\n", stack_pop(&s)); // Prints 3
    printf("%d\n", stack_pop(&s)); // Prints 0
    stack_fini(&s);

    return 0;
}