【问题标题】:Is there a way to prevent padding of a composed class有没有办法防止组合类的填充
【发布时间】:2016-02-01 01:17:48
【问题描述】:

假设是 64 位机器:

如果我从 A 类开始:

struct A
{
    int* a1; //8
    int* a2; //8 
    uint16_t a3; //2
    uint16_t a4; //2

    uint32_t a5; //4
    uint32_t a6; //4
    uint32_t a7; //4
};

现在所有单个成员都正确对齐了,A 的大小为 32 字节,a5a1 的偏移量为 20 字节

现在,如果我尝试将其重构如下:

struct A_part1
{
    int* a1; //8
    int* a2; //8 
    uint16_t a3; //2
    uint16_t a4; //2
};

struct A_new
{
    A_part1 a1; 
    uint32_t a5; //4
    uint32_t a6; //4
    uint32_t a7; //4
};

现在A_new 的大小为 40 字节,因为 A_part1 被填充到 24 个字节,A_new 随后从 36 填充到 40 个字节。

大概编译器正试图确保连续的A_part1 将对齐。

如果我知道我只会在A 中使用A_part1,这是我必须在a_part1 上使用pragma 包的唯一选择,以确保A_new 具有相同的特征和原来的A一样吗?

【问题讨论】:

  • 请注意,a) pragma 通常不被认为是可移植的。 2) 不同的编译器可能有相似的编译指示,但它们的名称不一定相同。 3) 编译器选项(即优化级别)与以前的设置相比可能会更改布局,您可能需要测试编译指示是否仍然执行您想要的操作。另一方面,我在 C++ 中使用 pragma 的经验通常是它们有效。

标签: c++ padding


【解决方案1】:

您是对的:标准 C++ 中无法指定 structs 的填充布局,因此您只能使用特定于编译器的控制机制,例如 #pragma pack

【讨论】:

  • @skimon 有几种方法可以指定内存结构/类的布局和填充。 Pragma 可能很简单,但通常不可移植,而且我还没有评估 pragma 在编译器选项更改时的行为。我在下面的回答是我在生产代码中使用的 3 种“标准 C++”技术(即可移植且无编译指示)之一。
【解决方案2】:

如果我知道我只会在 A 中使用 A_part1,那么我唯一的选择是 此处必须在 a_part1 上使用编译指示包以确保 A_new 将 和原来的A有一样的特点吗?

没有。 pragma 不是指定布局和填充的唯一方法。

在我曾经开发过的一个嵌入式软件中,我们找到了几种解决编译指示问题的方法。


C 和 C++ 都不提供内存布局语义。

但至少在逐个字段的基础上,一种易于理解的技术是显式编码字节驻留在类或结构中的位置。通过重构和添加小字节移动方法,工作量大大减少。

注意:七个字段很小,因此很“容易”。五十田很累。数百个字段是“让我们写一些代码来写一些代码”。你的容忍度会有所不同。

这种技术将字节显式地逐个字节地移动到类数据中。优点:1)没有填充(除非你想要它)。 2) 对编译器选项更改不敏感。 3) 便携。

小例子(编译,但未测试)(注意:任意字节序选择,我们使用传统的字节序转换)

class A
{
   //   int* a1;     //8  0..7
   //   int* a2;     //8  8..15
   //   uint16_t a3; //2  16..17
   //   uint16_t a4; //2  18..19
   //
   //   uint32_t a5; //4  etc
   //   uint32_t a6; //4
   //   uint32_t a7; //4
   // ...

   // modify field a3
   void a3(uint16_t val) {
      // if needed, correct val for destination endianess ------vvv
      data[Oa3+0] = static_cast<uint8_t>((val >> 0) & 0xff); // LSB
      data[Oa3+1] = static_cast<uint8_t>((val >> 8) & 0xff); // MSB
   }

   // access field a3
   uint16_t  a3() {
      // if needed, correct val for endianess ---------------------vvv
      uint16_t val = static_cast<uint16_t>((data[Oa3+0] << 0) + // LSB
                                           (data[Oa3+8] << 8)); // MSB
      return (val);
   }

   // continue for each field

private:
   enum {
      Oa1 = 0, // Offset a1
      Oa2 = 8,
      Oa3 = 16
      //... etc
   };

   uint8_t data[(8+8+2+2+(3*4))]; 
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-24
    • 1970-01-01
    • 2019-02-15
    • 2020-10-10
    • 2018-02-04
    • 2016-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多