【问题标题】:Conditional struct type in CC中的条件结构类型
【发布时间】:2012-01-05 22:29:57
【问题描述】:

我只想解析一些可以以两种类型之一存在的数据。第一个是

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

有人知道吗?

【问题讨论】:

  • 您会在网站上找到许多关于让 c 进行面向对象编程的问题。尽管您不能完全满足您的要求,但您正以此为起点。也就是说,它通常比它的价值更麻烦。对于一个真正的项目,为什么不直接使用 c++?毕竟,多态性是它添加的东西之一。
  • @dmckee - 你怎么知道这不是一个“真正的项目”?
  • 我猜你是对的。使用普通 C 似乎这几乎是不可能的。我只是不想放弃:-)
  • @James:我不知道,我也不做任何判断。我调整这个建议是因为彼得可能正在为他自己的启迪而解决这个问题,在这种情况下,这个建议是毫无意义的。
  • 函数名说你想解析数据,但是你的示例函数说你想做相反的事情(即serialise)——这个可能的解决方案有很大的不同。你真正需要哪一个?

标签: c parameters struct conditional


【解决方案1】:

这取决于您的解决方案必须有多通用。正如其他答案所确定的,这两个示例结构非常相似,因此可以相对容易地管理(尽管决定如何确定字符串的结尾存在一些问题)。

如果您需要更通用的系统,您可能需要查看传递给转换器的某种“结构描述符”字符串,或者可能需要查看“结构描述符数组”。

例如,字符串可能是:

"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 中鲜为人知的宏之一,即在 <stddef.h> 中定义的 offsetof 宏。您将创建一个描述符类型,例如:

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() 函数看起来更像是一个打印函数。


另见

【讨论】:

  • 就是这样,伙计 :-) 谢谢。我可以使用 offsetof 运算符通过计算指针偏移量来重新对齐结构。 :-) 我会试试这个。以前从未听说过/读过那个操作员...
  • 我想到的上面的另一个变体是将一个指针数组传递给将从结构中检索适当成员的函数。有点像 C++ 对象,但虚拟调度表与对象本身是分开的。
  • @Neil:是的 - 这是一个有效的变体。我在句子“[i]而不是使用枚举,您可以使用指向正确转换器的函数指针类型”中提到了它。
  • 对不起,我误解你的意思是指向处理适当类型结构的函数的指针。
【解决方案2】:

嗯,这两个结构之间的唯一区别是name 成员中的字符数。如果您的结构将其保留为char*(在内存中),那么我认为您想要的会起作用。当id 被读取后,您可以malloc 适当的大小并读取它,然后读取结构的其余部分。

【讨论】:

  • 上面的例子只是为了解释问题。我真正想做的是解析以特定格式给出的 PE 文件(exe、dll、..),这些文件与上面显示的 32 位和 64 位文件之间的差异完全相同。所以没有办法修改布局以便于解析。
  • 您是否担心 128 位文件的发明?您要阅读哪些 PE 结构?
  • 我正在尝试读取可选的标头部分,其中 imageBase 地址可以是 32 位或 64 位宽度。
【解决方案3】:

你当然可以

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);
  /* ... */
}

但它有点尴尬和重复。

【讨论】:

  • 重复是我想要保存的。当我说 60 行来转换 30 个值并打印它们时,我可以使用这 60 行来打印值。
【解决方案4】:

您可以在数组的结构中使用联合,因此它的大小将取决于分配。但是我不知道它如何与您的打包属性一起使用。

【讨论】:

    【解决方案5】:

    每个成员访问都需要知道结构布局。而且因为您不知道提前使用哪种结构,所以您将不得不以某种形式复制代码以处理两种布局。但是,如果您有已知数量的结构,则可以将细节隐藏在宏后面:

    #define MEMBER(_ptr, _type, _name) ((_type)?((A*)_ptr)->_name:((B*)_ptr)->_name)
    
    printf("%i\n", MEMBER(a, type, value1));
    

    【讨论】:

      【解决方案6】:

      我发现有些答案过于复杂。

      在我看来,解决这个问题的最简单方法是在数据结构中使用 联合。 对于您在示例中提供的那个,它应该是这样的:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-11-23
        • 1970-01-01
        • 1970-01-01
        • 2011-02-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多