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

时间:2014-01-15 22:57:25

标签: c++ c

我目前有以下结构

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

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

如果我想要结构的大小是准确的(在这种情况下为5),我将如何以“可扩展”的方式进行计算(即不执行sizeof(foo.bar) + sizeof (foo.baz)之类的操作)?

2 个答案:

答案 0 :(得分:3)

在标准C中没有办法计算结构成员的大小总数而不在源代码中列出它们(这可以通过预处理器功能或使用其他代码[编译和执行]的一些帮助来完成在最终代码的编译时]生成最终代码)。这是因为标准C没有提供任何方法来迭代结构的成员或以其他方式检查其组成而不使用其成员的名称。

我会对C ++提出同样的主张,除了模板和诸如此类的东西可能会出现一些可怕的黑客攻击。我倾向于那是不可能的,但我还没有检查最新C ++标准中的所有新功能。

在任何情况下,计算总大小对于序列化和反序列化成员的目的(即将成员转换为网络包中的字节,反之亦然)是不够的,因为您仍然需要单独转换成员,不仅仅知道他们的大小。

接近目标的选项包括:

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

答案 1 :(得分:1)

“......以可扩展的方式?” - 没有办法,C没有内存布局语义。

注意: 我从未发现编译器为“sizeof(T)”提供了不正确的值,

编译器告诉你一个N struct foo对象的数组, 正如您在此处所介绍的那样,将占用8 * N字节。

然而,我有一个项目,编码强制'正确', 预期的,我们应该说一个对象的最紧凑的“包装”, 没有使用pragma。 (我认为pragma是 非便携。但是,我调查过的每个编译器 其中包含pragma,似乎提供了很好的结果。该 pragma偶尔也不会拼写相同的内容)

换句话说,如果你真的需要一个5字节的foo对象,那么我认为这样做是相对容易的。但是如果你的话,以下方案很快就会变得很烦人 对象foo增长到拥有很多领域。

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

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

我们尝试了许多解决方案,每个解决方案都有专业和解决方案。

以下是在5个成功机制中的1个之后设计的......这个很容易记住。

两个步骤:

  • 使用byte数组声明对象。
  • 添加适当的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]; };
   };
}

我的一些团队试图创建一个联盟...... 并发现它在工会时变得混乱 必须匹配远程编译器,目标,endian-ness 在本地主机和编译器上。有点困惑 模拟器也是。