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

时间:2016-09-24 17:18:35

标签: c variadic-functions c11

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

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

但是,我正在考虑使用varargs传递输入:

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

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

我的问题是:使用相同大小的不同类型访问vararg成员是否有效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);
}

2 个答案:

答案 0 :(得分:2)

按照您的意图行事,引发未定义的行为。

来自C标准(C11草案):

  

7.16.1.1 va_arg宏

     

<强>概要

     

1

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

<强>描述

     

2 [...]如果    type 与实际下一个参数的类型不兼容(根据   对于默认参数提升),行为是未定义的,除了以下内容   例:

     

- 一种类型是有符号整数类型,另一种类型是相应的无符号整数   类型,值可以在两种类型中表示;

     

- 一种类型是指向void的指针,另一种是指向字符类型的指针。

这两个例外似乎都不符合您的用例。

答案 1 :(得分:1)

要进一步回答我自己的问题:不,不可能以char[] sizeof type获取varargs:

    type中的
  • va_arg(ap, type);不能是数组。
  • type可以是struct,但struct不能拥有VLA。
  • 对于GCC,它希望VLA struct作为指针传递。
  • 多次调用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);
}

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

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