除了使用 pragma pack aur 位字段外,我们如何避免 C 中的结构填充?有没有其他可用的方法?
3 回答
在结构开始时打包最大的物品。将较小的物品打包到最后。
struct optimal_packing
{
double d;
int i[4];
short j[3];
char s[24];
};
更准确地说,对齐要求最严格的项目需要最早出现(往往是指针和double
或可能long double
),以及最后对齐要求不太严格的项目(short
和char
)。如果组件的总长度加起来例如 35 个字节,您仍然可以使用尾部填充,但其中一种类型需要 8 字节对齐;会有5个字节的填充。
避免结构填充的唯一完全可移植且可靠的方法是根本不使用真实成员struct
。使用单个char
数组成员,并定义访问其内容的宏:
struct paddingless {
// char *p;
// double d;
// char c;
#define OFFSET_P 0
#define OFFSET_D (OFFSET_P + sizeof(char *))
#define OFFSET_C (OFFSET_D + sizeof(double))
#define OFFSET_END (OFFSET_C + sizeof(char))
char data[OFFSET_END];
};
便携式 getter 和 setter 如下所示:
inline double paddingless_get_d(const struct paddingless *o) {
double val;
memcpy(&val, o->data + OFFSET_D, sizeof(val));
return val;
}
inline void paddingless_set_d(struct paddingless *o, double val) {
memcpy(o->data + OFFSET_D, &val, sizeof(val));
}
如果您知道您的体系结构接受未对齐的访问,您可以摆脱使用强制转换定义的 setter:
#define paddingless_get_d(o) (*(double *) ((o)->data + OFFSET_D))
#define paddingless_set_d(o, val) (*(double *) ((o)->data + OFFSET_D) = (val))
这是不可移植的,但可能比替代方案更快。它适用于vax x86 ...
有一些事情需要考虑。首先,编译器添加填充是有原因的:它试图为其指定平台生成最佳的工作机器代码。所以通常填充是一件好事。除了要定义数据通信协议、硬件寄存器映射等情况外,字节数必须与特定规范匹配。
确实没有避免填充的标准方法。如果结构大小与其所有单个成员的总和不匹配,您可以实现的最好的结果是编译时断言给出错误。当断言失败时,您可以将编译器设置更改为块填充。最好:
static_assert(sizeof(mystruct) == (sizeof(mystruct.x) + sizeof(mystruct.y) +...));
如果您的 C 编译器中没有 static_assert(您需要一个兼容 C11 的编译器),那么使用一些“ct_assert”宏,您可以在此站点上找到很多内容。
但是,这并不能以可移植的方式解决问题,您将依赖于编译器设置。解决问题的唯一真正可移植的方法是这样的:
void mystruct_copy_from (uint8_t* raw_data, const mystruct_t* ms)
{
memcpy(raw_data, &ms->x, sizeof(ms->x));
raw_data += sizeof(ms->x);
memcpy(raw_data, &ms->y, sizeof(ms->y));
raw_data += sizeof(ms->y);
// ... and so on
}
uint8_t protocol [EXPECTED_STRUCT_SIZE];
mystruct_copy_from (protocol, &mystruct);
send(protocol); // some data communication interface without padding