【问题标题】:getting a substruct out of a big struct in C从 C 中的大结构中获取子结构
【发布时间】:2009-05-19 13:59:25
【问题描述】:

我在现有程序中有一个很大的struct。该结构包含大量位域。

我希望保存其中的一部分(例如,150 个字段中的 10 个)。

我用来保存子类的示例代码是:

typedef struct {int a;int b;char c} bigstruct;
typedef struct {int a;char c;} smallstruct;
void substruct(smallstruct *s,bigstruct *b) {
    s->a = b->a;
    s->c = b->c;
}
int save_struct(bigstruct *bs) {
    smallstruct s;
    substruct(&s,bs);
    save_struct(s);
}

我也希望选择它的哪一部分不会太麻烦,因为我希望时不时地改变它。我之前提出的幼稚方法非常脆弱且无法维护。当扩展到 20 个不同的字段时,您必须同时更改 smallstructsubstruct 函数中的字段。

我想到了两种更好的方法。不幸的是,两者都需要我使用一些外部 CIL 之类的工具来解析我的结构。

第一种方法是自动生成substruct 函数。我将设置smallstruct的结构,并有一个程序会解析它并根据smallstruct中的字段生成substruct函数。

第二种方法是(使用 C 解析器)构建关于 bigstruct 的元信息,然后编写一个库,允许我访问结构中的特定字段。这就像 Java 类反射的临时实现。

例如,假设没有结构对齐,对于结构

struct st {
    int a;
    char c1:5;
    char c2:3;
    long d;
}

我将生成以下元信息:

int field2distance[] = {0,sizeof(int),sizeof(int),sizeof(int)+sizeof(char)}
int field2size[] = {sizeof(int),1,1,sizeof(long)}
int field2bitmask[] =  {0,0x1F,0xE0,0};
char *fieldNames[] = {"a","c1","c2","d"};

我会用这个函数得到ith 字段:

long getFieldData(void *strct,int i) {
    int distance = field2distance[i];
    int size = field2size[i];
    int bitmask = field2bitmask[i];
    void *ptr = ((char *)strct + distance);
    long result;
    switch (size) {
        case 1: //char
             result = *(char*)ptr;
             break;
        case 2: //short
             result = *(short*)ptr;
        ...
    }
    if (bitmask == 0) return result;
    return (result & bitmask) >> num_of_trailing_zeros(bitmask);
 }

这两种方法都需要额外的工作,但是一旦解析器在您的 makefile 中 - 更改子结构是轻而易举的事。

不过,我宁愿在没有任何外部依赖的情况下这样做。

有人有更好的主意吗?在我的想法有什么好处的地方,我的想法在互联网上是否有一些可用的实施方式?

【问题讨论】:

  • 你的方向很好,但是宏可以帮助描述结构成员,使用#name(字符串化)和 f1##name(连接)

标签: c data-structures struct bit-fields


【解决方案1】:

根据您的描述,您似乎可以访问并修改您的原始结构。我建议您将子结构重构为一个完整的类型(就像您在示例中所做的那样),然后将该结构作为大结构上的一个字段,将原始结构中的所有这些字段封装到较小的结构中。

扩展你的小例子:

typedef struct 
{
  int a;
  char c;
} smallstruct;

typedef struct 
{
  int b;
  smallstruct mysub;
} bigstruct;

访问 smallstruct 信息的方法如下:

/* stack-based allocation */
bigstruct mybig;
mybig.mysub.a = 1;
mybig.mysub.c = '1';
mybig.b = 2;

/* heap-based allocation */
bigstruct * mybig = (bigstruct *)malloc(sizeof(bigstruct));
mybig->mysub.a = 1;
mybig->mysub.c = '1';
mybig->b = 2;

但你也可以传递指向小结构的指针:

void dosomething(smallstruct * small)
{ 
  small->a = 3;
  small->c = '3';
}

/* stack based */    
dosomething(&(mybig.mysub));

/* heap based */    
dosomething(&((*mybig).mysub));

好处:

  • 没有宏
  • 没有外部依赖
  • 没有内存顺序转换技巧
  • 更简洁、更易于阅读和使用的代码。

【讨论】:

    【解决方案2】:

    如果更改字段的顺序不是不可能的,您可以重新排列 bigstruct 字段,使 smallstruct 字段在一起,然后只需从一个转换到另一个(可能添加偏移量)。 比如:

    typedef struct {int a;char c;int b;} bigstruct;
    typedef struct {int a;char c;} smallstruct;
    
    int save_struct(bigstruct *bs) {
        save_struct((smallstruct *)bs);
    }
    

    【讨论】:

    • 这要求所有子集字段始终是大结构中定义的第一个字段。如果它们分散在整个大结构中,就会出错。
    • @rikh:我在开篇就说了这么多。但如果是他的代码,改变成员变量的顺序既简单又安全(没有什么理智的人应该这样做)
    • @Blindy:如果代码是理智的,我就不会有大的结构;-)。这可以工作。
    【解决方案3】:

    宏是你的朋友。

    一种解决方案是将大结构移出到它自己的包含文件中,然后有一个宏派对。

    不是正常定义结构,而是选择宏,例如BEGIN_STRUCTURE、END_STRUCTURE、NORMAL_FIELD、SUBSET_FIELD

    然后,您可以多次包含该文件,为每次传递重新定义这些结构。第一个会将定义转换为正常结构,两种类型的字段都正常输出。第二个将定义 NORMAL_FIELD 什么都没有,并将创建您的子集。第三个将创建适当的代码来复制子集字段。

    您最终会得到结构的单一定义,它可以让您控制子集中的字段并自动为您创建合适的代码。

    【讨论】:

    • 我不会贬低你,但宏是你的恶魔,而不是你的朋友。只是想提一下。 :)
    【解决方案4】:

    为了帮助您获取元数据,您可以参考 offsetof() 宏,它还具有处理您可能拥有的任何填充的好处

    【讨论】:

    • offsetof 宏不适用于位域。没有找到任何类似的东西。
    【解决方案5】:

    我建议采用这种方法:

    1. 诅咒编写大结构的人。买一个巫毒娃娃,玩得开心。
    2. 以某种方式标记您需要的大结构的每个字段(宏或注释或其他)
    3. 编写一个读取头文件并提取标记字段的小工具。如果您使用 cmets,您可以为每个字段指定优先级或对其进行排序。
    4. 为子结构编写新的头文件(使用固定的页眉和页脚)。
    5. 编写一个新的 C 文件,其中包含一个函数 createSubStruct,该函数接受一个指向大结构的指针并返回一个指向子结构的指针
    6. 在函数中,循环收集收集的字段并发出ss.field = bs.field(即,一一复制字段)。
    7. 将小工具添加到您的 makefile 中,并将新的头文件和 C 源文件添加到您的构建中

    我建议使用gawk 或任何您熟悉的脚本语言作为工具;这应该需要半小时才能构建。

    [编辑] 如果你真的想尝试反射(我不建议这样做;要在 C 中工作需要做很多工作),那么 offsetof() 宏就是你的朋友。此宏返回结构中字段的偏移量(通常不是它之前的字段大小的总和)。见this article

    [EDIT2] 不要编写自己的解析器。让你自己的解析器正确需要几个月的时间;我知道,因为我一生中写过很多解析器。而是标记原始头文件中需要复制的部分,然后依赖您知道可以工作的一个解析器:您的 C 编译器之一。以下是一些如何使这项工作发挥作用的想法:

    struct big_struct {
        /**BEGIN_COPY*/
        int i;
        int j : 3;
        int k : 2;
        char * str;
        /**END_COPY*/
        ...
        struct x y; /**COPY_STRUCT*/
    }
    

    只需让您的工具复制/**BEGIN_COPY*//**END_COPY*/ 之间的任何内容。

    使用 /**COPY_STRUCT*/ 等特殊 cmets 来指示您的工具生成 memcpy() 而不是赋值等。

    这可以在几个小时内编写和调试。为没有任何功能的 C 设置解析器需要很长时间;也就是说,你只需要一些可以读取有效 C 的东西,但你仍然需要编写解析器中理解 C 的部分,以及对数据做一些有用的部分。

    【讨论】:

    • 这是个好主意,但我不喜欢不解析 C 文件的脆弱性。我知道 offsetof 宏,但在我使用位域后,offetof 宏与我分手了 :-)
    • 我已经为我自己的语言编写了几个 C 解析器和解析器以及一个 XML 解析器。编写解析器至少需要一周时间。一周之后,你就有了一些可以构建并且可以理解简单案例的东西。对于这项任务,我认为编写能够解析足够多的 C 来解决您的问题的东西大约需要一个月的时间。结论:除非你有足够的时间,否则不要走那条路。
    猜你喜欢
    • 1970-01-01
    • 2011-05-13
    • 2013-06-19
    • 1970-01-01
    • 1970-01-01
    • 2020-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多