如何用c语言用偏移量设计结构元素



我想在更短的时间内通过给定的偏移量来设计c结构,并且易于修改。

让我们举个例子,

假设我的结构如下:

#pragma pack(push, 1)
typedef struct 
{
char dummy[15]; // offset 0
unsigned int field1; // offset 15
char dummy[45]; // offset 19
unsigned int field2; // offset 64
char dummy[25]; // offset 68
unsigned int field3; // offset 93
}
#pragma pack(pop)

我的想法是找到一种方法,给开发人员(对我来说)一种轻松编写结构的方法。例如,给定的偏移量想要像这样声明结构:

#define SET_FIELD ???
#pragma pack(push, 1)
typedef struct 
{
SET_FIELD(unsigned int, field1, 15); 
SET_FIELD(unsigned int, field2, 64);
SET_FIELD(unsigned int, field3, 93);
}
#pragma pack(pop)

当然,SET_FIELD"定义将自动填充有人知道吗?谢谢你,

这似乎是一个坏主意,但您可以通过滥用联合和匿名结构成员来实现:

#define SET_FIELD(Type, Name, Offset) 
struct { unsigned char Padding##Offset[Offset]; Type Name; }
#pragma pack(push, 1)
typedef union
{
SET_FIELD(unsigned int, field1, 15); 
SET_FIELD(unsigned int, field2, 64);
SET_FIELD(unsigned int, field3, 93);
} ManualStructure;
#pragma pack(pop)

#include <stddef.h>
#include <stdio.h>

int main(void)
{
printf("field1 is at offset %zu.n", offsetof(ManualStructure, field1));
printf("field2 is at offset %zu.n", offsetof(ManualStructure, field2));
printf("field3 is at offset %zu.n", offsetof(ManualStructure, field3));
}

上面的输出是:

field1的偏移量为15。Field2位于偏移量64。Field3位于偏移量93。

用于填充的数组名为Padding##Offset,这一事实也会提醒您错误地使用相同的偏移量,因为这会导致两个具有相同名称的成员。但是,它不会警告您部分重叠。

您也可以使用GCC和Clang的属性功能来代替#pragma:

#define SET_FIELD(Type, Name, Offset) 
struct __attribute__((__packed__)) { unsigned char Padding##Offset[Offset]; Type Name; }
typedef union
{
SET_FIELD(unsigned int, field1, 15); 
SET_FIELD(unsigned int, field2, 64);
SET_FIELD(unsigned int, field3, 93);
} ManualStructure;

一个问题是,C标准允许对联合的任何成员进行写操作,从而影响与该成员不对应但与其他成员对应的字节。例如,给定ManualStructure x,赋值x.field1 = 3;可以改变x.field3的字节,因为这些字节不对应于包含x.field1的结构的字节。您可以通过在每个匿名结构中包含一个额外的成员来解决这个问题,该成员将其字节填充到ManualStructure的完整长度。然后,无论何时写入字段,都是在写入字节占据整个联合的成员,因此没有不对应于该成员的成员。然而,要做到这一点,您必须事先知道结构的总大小,或者至少能够为它选择一些边界。

前提:这样做可能会适得其反,如果您愿意告诉我们为什么需要在特定偏移量处指定成员,则可以用另一种方式解决。

实际答案:如果我有你同样的问题,我会使用外部脚本语言来解决它,以便在编译代码之前生成struct定义。

例如,您可以在.c.h文件中键入一个特殊的注释,如:

///DEFSTRUCT: mystruct (unsigned int, field1, 15) (unsigned int, field2, 25)

然后我会用JavaScript/Python/Perl/任何简单地读取.c.h文件的快速脚本,找到以///DEFSTRUCT:开始的行,然后将它们替换为正确包装的struct定义。

在我看来,在没有外部脚本语言的情况下这样做将是一个伟大的PITA。另外,调试JavaScript/Python程序比调试C预处理器要容易得多。

这是可行的,但是您需要使用单个宏来定义整个结构体。

这个实现的问题是,它在末尾产生一个零大小的填充数组(可以用更复杂的宏来消除),在相邻字段之间产生一个零大小的数组(无法帮助)。我不认为标准C允许零大小数组,但是一些编译器接受它们。

run on gcc.godbolt.org

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_END
#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x##y
#define MAKE_STRUCT(seq) char padding_first[ END( MAKE_STRUCT_LOOP_A seq ) * 0 ];
#define MAKE_STRUCT_LOOP_A(...) MAKE_STRUCT_LOOP_BODY(__VA_ARGS__) MAKE_STRUCT_LOOP_B
#define MAKE_STRUCT_LOOP_B(...) MAKE_STRUCT_LOOP_BODY(__VA_ARGS__) MAKE_STRUCT_LOOP_A
#define MAKE_STRUCT_LOOP_A_END
#define MAKE_STRUCT_LOOP_B_END
#define MAKE_STRUCT_LOOP_BODY(type, name, offset) 
+ (offset)]; 
type name; 
char CAT(padding_after_,name)[-(offset + sizeof(type))
struct A
{
MAKE_STRUCT(
(unsigned int, field1, 15)
(unsigned int, field2, 64)
(unsigned int, field3, 93)
)
};

展开为:

struct A
{
char padding_first[ + (15)]; // 15
unsigned int field1;
char padding_after_field1[-(15 + sizeof(unsigned int)) + (64)]; // 45
unsigned int field2;
char padding_after_field2[-(64 + sizeof(unsigned int)) + (93)]; // 25
unsigned int field3;
char padding_after_field3[-(93 + sizeof(unsigned int)) * 0 ]; // 0
};

使用N次(这里N为3)的单个宏是不可行的。

考虑这个

SET_FIELD(unsigned int, field3, 93);

,你想把它变成

char dummy[25];
unsigned int field3;

现在宏如何从数字93获得25(即char dummy[25];)的填充大小?它不能……这需要了解以前使用SET_FIELD宏的所有知识。

如果你真的想要这样的东西,你可以写一个单独的程序来解析你想要的结构定义语法,并为你的C项目生成一个普通的包含文件(.h文件)。

最新更新