准确计算编译器在结构中添加的填充

Accurately calculate the compiler added padding in a struct

本文关键字:添加 填充 结构 计算 编译器      更新时间:2023-10-16

我目前有以下结构

struct foo {
u32 bar;
u8 baz[1];
}

由于填充,sizeof(struct foo)结果为8。

如果我想要结构的大小是准确的(在本例中为5),我将如何以"可缩放"的方式进行计算(即不执行类似sizeof(foo.bar) + sizeof (foo.baz)的操作)?

在标准C中,如果不在源代码中列出结构成员的大小,就无法计算结构成员的总大小(这可以在预处理器功能的帮助下完成,也可以使用其他代码[在最终代码的编译时编译并执行]来生成最终代码)。这是因为标准C没有提供任何方法来迭代结构的成员,或者在不使用其成员名称的情况下检查其组成。

我会对C++做出同样的声明,只是模板之类的东西可能会有一些可怕的黑客攻击。我倾向于这是不可能的,但我还没有研究最新C++标准中的所有新功能。

在任何情况下,计算总大小都不足以实现序列化和反序列化成员的既定目的(即,将成员转换为网络包中的字节,反之亦然),因为您仍然需要单独转换成员,而不仅仅是知道其大小的总和。

实现目标的选项包括:

  • 使用特定于实现的功能来打包结构,使其不包含填充。然后,您可以写入和读取结构的字节,以执行序列化和反序列化。您仍然需要确保发送和接收系统之间的类型匹配(相同宽度的整数、相同格式的浮点值、相同的字符集编码、)
  • 编写用于运行时处理所需类型的代码。它将以某种格式(如类型列表)对结构进行描述,并为每种类型都有大小写。每个案例都包含该类型的编译时代码(例如sizeof(float)),但代码会在运行时在案例之间进行调度。因此,您需要按照此代码使用的格式准备结构的描述

"…以可扩展的方式?"——不行,C没有内存布局语义。

注:我从来没有找到一个编译器为"sizeof(T)",提供错误的值

编译器告诉你一个由N个struct-foo对象组成的数组,正如您在这里介绍的,将占用8*N个字节。

然而,我在一个项目中,编码为强制使用"适当",预期的,我们可以说是物体的最紧凑的"包装",而不使用杂注。(我认为实用主义者非便携式。然而,我研究过的每一个编译器其中包含了pragmas,似乎提供了极好的结果。这个pragma偶尔拼写不一样)

换句话说,如果您真的需要一个5字节的foo对象,那么实现它是可能的,而且我认为相对容易。但是,如果你对象foo增长到有很多字段。

在我的嵌入式软件项目中,远程处理器在不同的构建机器上使用了不同的处理器和编译器,他们使用了编译器的pragma包。

我们的软件必须描述一个符合其包装的对象,因为数千个子系统已经发货。我们的代码必须能够进行交互。这些系统通过专用背板发送的二进制数据包进行通信。(C和C++都没有提供内存布局符号。Ada提供了,但有人在乎了吗?)

我们尝试了许多解决方案,每个方案都有赞成和反对。

以下是根据5种成功机制中的1种而设计的。。。这个很容易记住。

两个步骤:

  • 使用字节数组声明对象
  • 添加适当的setter和getter,每个字段各1个

注意:您将学习endian-ness,查看宏的ntoh和hton

namespace fooNameSpace 
{
struct foo
{
private:
uint8_t pfd[5]; //packed_foo_data
public:
void barSet(uint32_t value)
{
pfd[0] = static_cast<uint8_t>((value & 0xff000000) >> 24);  // msbyte
pfd[1] = static_cast<uint8_t>((value & 0x00ff0000) >> 16);
pfd[2] = static_cast<uint8_t>((value & 0x0000ff00) >> 8);
pfd[3] = static_cast<uint8_t>((value & 0x000000ff) >> 0);  // lsbyte
}; // you will need to fix for endian of target
uint32_t barGet(void)
{
uint32_t value = 0;
value |= pfd[0] << 24; // msbyte
value |= pfd[1] << 16;
value |= pfd[2] << 8;
value |= pfd[3] << 0; // lsbyte
return (value);
}; // fix for endian of target
void     bazSet(uint8_t value) { pfd[4] = value; }      
uint8_t  bazGet(void)          { return pfd[4]; };
};
}

我的一些团队试图创建一个联盟。。。发现当工会必须匹配远程编译器、目标、端序在本地主机和编译器上。有点困惑模拟器。