从C中的大结构体中获取子结构体

3

我在一个现有程序中有一个非常大的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个不同字段时,必须在和函数中同时更改字段。
我考虑了两种更好的方法。不幸的是,两种方法都需要我使用一些外部类似于CIL的工具来解析我的结构体。
第一种方法是自动生成函数。我将设置结构,并编写一个程序来解析它并根据中的字段生成函数。
第二种方法是使用C解析器构建有关的元信息,然后编写一个库,允许我访问结构体中的特定字段。这将类似于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"};

我将使用以下函数获取第 i 个字段:
```python getField(i) ```
该函数将返回第 i 个字段的值。
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);
 }

这两种方法都需要额外的工作,但是一旦解析器在您的制作文件中 - 更改子结构就变得非常容易。

然而,我更愿意在没有任何外部依赖项的情况下做到这一点。

有没有更好的想法?我的想法是否好用,互联网上是否有可用的实现?


你走在正确的方向上,但宏可以帮助描述结构成员,使用#name(将其转换为字符串)和f1##name(进行连接)。 - 0x6adb015
5个回答

12

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

扩展您的小示例:

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

好处:

  • 无需宏
  • 无外部依赖
  • 无内存顺序转换技巧
  • 代码更加清晰易读易用。

3
如果可以改变字段的顺序,您可以重新排列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);
}

1
这要求所有子集字段始终是大结构中定义的第一个字段。如果它们分散在整个大结构中,那么就会出现问题。 - Rik Heywood
@rikh:我在开头的句子中已经说过了。但是,如果这是他的代码,改变成员变量的顺序既容易又安全(实际上不应该因此出现任何问题)。 - Blindy
@Blindy:如果这段代码是合理的,我就不用整个大结构了;-)这可能有效。 - Elazar Leibovich

1

宏是你的好朋友。

一种解决方案是将大型结构体移动到自己的包含文件中,然后进行宏处理。

不要像通常定义结构体那样,而是想出一些宏,例如BEGIN_STRUCTURE、END_STRUCTURE、NORMAL_FIELD、SUBSET_FIELD等。

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

最终你将得到一个结构的单一定义,让你控制哪些字段在子集中,并自动为你创建合适的代码。


1
我不会给你点踩,但宏并不是你的朋友,而是你的敌人。只是想提一下。 :) - Randolpho

0

我建议采取以下方法:

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

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

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

[编辑2] 不要编写自己的解析器。要想正确编写自己的解析器需要数月时间,我知道这一点,因为我已经写了很多解析器。相反,标记原始头文件中需要复制的部分,然后依赖于你知道可行的一个解析器:你的 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*/,指示您的工具生成memcpy()而不是赋值等。

这可以在几个小时内编写和调试。如果没有任何功能,设置C解析器将需要同样长的时间;也就是说,您只能读取有效的C代码,但仍需要编写理解C代码的解析器部分以及对数据执行有用操作的部分。


这是一个好主意,但我不喜欢不解析C文件的脆弱性。我知道offsetof宏,但在使用位域后,offsetof宏和我分手了 :-) - Elazar Leibovich
我已经编写了几个C解析器和自己语言的解析器,还有一个XML解析器。编写解析器至少需要一周时间。在这一周之后,你会得到一个可以构建并能够理解简单情况的东西。对于这个任务,我估计需要大约一个月的时间来编写足以解析C语言以解决问题的程序。结论:除非你有充足的时间,否则不要走这条路。 - Aaron Digulla

0

只是为了帮助您获取元数据,您可以参考offsetof()宏,这还能处理可能出现的任何填充。


offsetof宏不适用于位域。没有找到等效的东西。 - Elazar Leibovich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接