Условный тип структуры в C - PullRequest
3 голосов
/ 25 ноября 2011

Я просто хочу проанализировать некоторые данные, которые могут присутствовать в одном из двух типов. Первый -

struct typeA {
  int id,
  char name[16],
  int value1,
  int value2,
  int value3,
  int value4
} __attribute__ ((packed));

Вторая возможность состоит в том, что данные имеют форму с двойным именем длины

struct typeB {
  int id,
  char name[32],
  int value1,
  int value2,
  int value3,
  int value4
} __attribute__ ((packed));

пока все хорошо. Теперь у меня есть две функции, которые разбирают эти две

int parse_typeA(struct typeA *x){ /* do some stuff */ }
int parse_typeB(struct typeB *x){ /* do some stuff */ }

Теперь это, очевидно, нецелесообразно, если у вас есть больше типов. Как я могу реализовать синтаксический анализ обоих типов, используя одну функцию и дополнительный параметр, такой как

int parse_any_type(void *x, int type){ 
    /*
     *  WHAT TO DO HERE ??
     *  
     *  The following doesn't work
     *  
     *  if(type == 1)
     *    struct typeA *a = (struct typeA *)x;
     *  else
     *    struct typeB *a = (struct typeB *)x;
     */
    printf("%i\n", a->id);
    printf("%s\n", a->name);
    printf("%i\n", a->value1);
    printf("%i\n", a->value2);
    printf("%i\n", a->value3);
    printf("%i\n", a->value4);
}

Кто-нибудь есть идеи?

Ответы [ 6 ]

2 голосов
/ 25 ноября 2011

Это зависит от того, насколько общим должно быть ваше решение.Как выяснили другие ответы, две примерные структуры чрезвычайно похожи, и поэтому ими можно относительно легко управлять (хотя решение о том, как определить конец строки символов, представляет некоторые проблемы).

Если вам нужен более общийсистема, вам, вероятно, потребуется посмотреть какую-то строку «структурный дескриптор», которую вы передаете в конвертер, или, возможно, «массив структурных дескрипторов».

Например, строки могут быть:

"i s16 i i i i"  // typeA
"i s32 i i i i"  // typeB

"u32 i64 z d d"  // struct { uint32_t a; int64_t b; size_t c; double d; double e; };

int parse_any_type(void *output, const char *desc);

Затем вам придется столкнуться с некоторыми проблемами выравнивания и дополнения, но (если вы получите правильные строки дескриптора), вы можете написать подпрограмму для обработки этого лота (упакованную или распакованную).

Используя 'дескрипторы', вы, вероятно, имеете дело с одним из менее известных макросов в C, макросом offsetof, определенным в <stddef.h>.Вы должны создать тип дескриптора, такой как:

enum Type { CHAR, UCHAR, SCHAR, STR, USTR, SSTR, SHORT, USHORT, INT, UINT, LONG, ULONG, ... };
struct descriptor
{
    enum Type  m_type;    // Code for the variable type
    size_t     m_size;    // Size of type
    size_t     m_offset;  // Offset of variable in structure
};

struct descriptor d_TypeA[] =
{
    { INT, sizeof(int), offsetof(TypeA, id)     },
    { STR,          16, offsetof(TypeA, name)   },
    { INT, sizeof(int), offsetof(TypeA, value1) },
    { INT, sizeof(int), offsetof(TypeA, value2) },
    { INT, sizeof(int), offsetof(TypeA, value3) },
    { INT, sizeof(int), offsetof(TypeA, value4) },
};

Затем вы можете передать соответствующий массив дескрипторов типов (и размер этого массива) в функцию вместе с указателем на то, куда должны быть переданы данные.быть сохраненным.

Вместо использования перечисления вы можете использовать тип указателя на функцию, который указывает на правильный преобразователь.

int parse_structure(void *output, const struct descriptor *desc, size_t n_desc);

Другой альтернативой является то, что вы просто имеете дело с каждым типом с помощьюсоответствующая функция, которая вызывает другие более простые функции для обработки каждого элемента структуры.

int parse_TypeA(TypeA *output)
{
    if (parse_int(&output->id)      == 0 &&
        parse_str(output->name, 16) == 0 &&
        parse_int(&output->value1)  == 0 &&
        parse_int(&output->value2)  == 0 &&
        parse_int(&output->value3)  == 0 &&
        parse_int(&output->value4)  == 0)
        return 0;
    ...diagnose error...
    return -1;
}

В ваших примерах неясно указано, откуда поступают данные, а не где они должны храниться.Это может не иметь значения, но повлияет на решение.Без аргументов может быть разумным ожидать, что данные будут считаны из стандартного ввода.В качестве альтернативы, у вас может быть строка, содержащая данные для анализа, возможно, тоже с длиной;это будут аргументы функции.

В ваших примерах не проиллюстрирована обработка ошибок;как вызывающий код узнает, было ли преобразование успешным или нет.

Если все сделано правильно, один и тот же механизм описания может использоваться как для синтаксического анализа, так и для механизмов печати - ваша функция parse_any_type() больше похожа на печатьфункция.


См. также

0 голосов
/ 25 ноября 2011

Я нахожу некоторые ответы слишком сложными.

С моей точки зрения, самый простой способ решения этой проблемы - использовать union в вашей структуре данных.Для того, что вы указали в своем примере, это должно быть что-то вроде:

struct typeU
{
    int id;
    int name_len;

    union
    {
        char _16[16];
        char _32[32];
    } name;

    int value1;
    int value2;
    int value3;
    int value4;
} __attribute__ ((packed));

Функция печати будет выглядеть примерно так:

void typeU_print(struct typeU *t)
{
    printf("%i\n", t->id);

    switch (t->name_len)
    {
        case 16:
            printf("%s\n", t->name._16);
        break;

        case 32:
            printf("%s\n", t->name._32);
        break;
    }

    printf("%i\n", t->value1);
    printf("%i\n", t->value2);
    printf("%i\n", t->value3);
    printf("%i\n", t->value4);
}
0 голосов
/ 25 ноября 2011

Каждый член доступа должен знать структуру структуры. А поскольку вы не знаете, какую структуру вы используете заранее, вам придется дублировать код в какой-либо форме, чтобы обрабатывать оба макета. Однако, если у вас есть известное количество структур, вы можете скрыть все детали макроса:

#define MEMBER(_ptr, _type, _name) ((_type)?((A*)_ptr)->_name:((B*)_ptr)->_name)

printf("%i\n", MEMBER(a, type, value1));
0 голосов
/ 25 ноября 2011

вы можете использовать объединение внутри структуры для массива, поэтому его размер будет определяться при назначении.однако я не знаю точно, как это будет работать с вашим упакованным атрибутом.

0 голосов
/ 25 ноября 2011

Вы, конечно, можете сделать

int parse_any_type(void *x, int type){ 
  int id;
  char *name;
  int value1;
  int value2;
  int value3;
  int value4;    

  if(type == 1) {
     id   = ((struct typeA*)x)->id;
     name = ((struct typeA*)x)->name;
     /* ... */
  } else {
     id = ((struct typeB*)x)->id;
     /* ... */
  }

  printf("%i\n", id);
  printf("%s\n", name);
  printf("%i\n", value1);
  /* ... */
}

, но это немного неловко и повторяется.

0 голосов
/ 25 ноября 2011

Ну, единственная разница между двумя структурами - это количество символов в элементе name. Если бы ваша структура содержала это как char* (в памяти), то, как вы думаете, вы хотели бы работать. После прочтения id вы можете malloc выбрать подходящий размер и прочитать его, а затем и остальную часть структуры.

...