2016-09-24 11 views
1

Я пишу библиотеку контейнера в C, и я хотел бы использовать с переменным числом аргументов в моей реализации, например, так:Получение 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 для доступа к членам Vararg с использованием другого типа с одинаковым размером?

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

Так как же вы намерены определить тип объектов? Непонятно, что вы просите, но подход выглядит подозрительно. – Olaf

+0

Вы всегда можете использовать 'char'-массивы для переноса чего угодно. – alk

+0

@akl Но это работает с 'varargs.h'? – YoYoYonnY

ответ

2

Выполнение, поскольку вы намерены провоцировать неопределенное поведение.

От С-Стандарт (С11 проект):

7.16.1.1 va_arg макро

Сводка

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

Описание

2 [...] если тип не совместим с типа фактического следующего аргумента (поощряемое по аргумента по умолчанию поощрений в), поведение не определено, за исключением следующих :

- один тип - целочисленный тип со знаком, другой тип - соответствующее целое число без знака , и это значение представляется в обоих типах;

- один тип - указатель на void, а другой - указатель на тип символа.

Ни одно из двух исключений, похоже, не соответствует вашему прецеденту.

+1

Можете ли вы придумать жизнеспособное решение? – usr2564301

+0

После некоторого тестирования я обнаружил, что 'item = va_arg (ap, struct wrapper);' ожидает _a pointer_ для 'struct wrapper'. Вы можете это объяснить? – YoYoYonnY

+0

После выполнения некоторого тестирования _more_ я обнаружил, что 'item = va_arg (ap, struct wrapper);' ожидает только указатель, когда 'struct wrapper' содержит массив переменной длины. Weird. – YoYoYonnY

1

Для дальнейшего ответа на свой вопрос: Нет, это не представляется возможным получить как с переменным числом аргументов char[] с sizeof type:

  • type в va_arg(ap, type); не может быть массивом.
  • type может быть struct, но struct s не может иметь VLA.
  • В случае GCC ожидается, что VLA struct s будет передан как указатели.
  • Несколько вызовов 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; 
} 
+0

Недостатком этого метода является то, что в случае 'stack (stack (int))' он бесполезно использует много 'int'. – YoYoYonnY