【问题标题】:Best practice for struct layout in C/C++C/C++ 中结构布局的最佳实践
【发布时间】:2020-06-27 14:17:04
【问题描述】:

我已阅读文章和 SO 答案,了解如果我们按照独立字段大小的递减顺序布局结构,我们如何切断编译器添加的填充。

例如:而不是像这样布置(L1)结构

   typedef struct dummy {
   char         c1;
   short        s1;
   int          i1;
   unsigned int ui1;
   double       d1;
   } dummy_t;

像这样创建布局(L2)

   typedef struct dummy {
   double       d1;   
   unsigned int ui1;
   int          i1;
   short        s1;
   char         c1;   
   } dummy_t;

我的问题是,与 L1 或任何其他布局相比,布局 L2 是否存在缺点?或者我可以将它纳入我的设计规则以始终使用 L2 吗?也是自然对齐规则

natural alignment = sizeof(T)

对于原始类型总是正确的?

【问题讨论】:

  • C 和 C++ 是不同的语言,C 中的 struct 与 C++ 中的不同。在 C++ 中,结构被推广到类。选择您的语言。
  • 我是从 C PoV 说的。但是我相信对齐和填充仍然适用于成员变量的 C++ 结构和类?
  • @RohitMat - 是的,对齐和填充适用于 C++ 结构和类类型。但是,访问控制开始发挥作用,并且会影响成员的顺序,从而影响填充。因此,C++ 中的良好实践(无论如何衡量)可能与 C 中的良好实践完全不同。如果您是从 C PoV 中谈论的,因此我建议删除 C++ 标记。
  • @Peter,我会删除标签。谢谢
  • @P__J__ C 中的有效struct 在编译为 C++ 时通常具有相同的内存布局。

标签: c memory-alignment


【解决方案1】:

只有在内存受限的系统上运行(您想限制每个元素的大小)时,您才应该考虑对齐/结构大小。这仅适用于您知道将存储大量此类结构并且使用的存储空间是一个问题的系统/设计。另一方面,对结构的内容进行逻辑分组可以使您的代码更加清晰。我总是倾向于清晰而不是性能,尤其是在早期设计阶段。如果您没有与性能相关的限制,那就更是如此。

关于性能,从 L2 访问“随机”内容可能会比从 L1 更差,因为内存缓存对齐约束也会发挥作用。 (例如,在一行中访问 struct L1 的 2 个元素可能比 L2 的 2 个元素更慢或更快)。对于此类优化,分析是了解哪种布局最好的唯一方法。

【讨论】:

  • 感谢您对清晰度的看法。我正在研究具有内存限制的系统,因此不得不问这个问题。在缓存方面,我不确定我是否明白你的意思。您是指来自 L1 或 L2 的对象超出缓存行并由于访问行外字段而影响性能的情况吗?
  • 你好。是的,取决于结构本身的对齐方式及其自身的大小与缓存行大小(它是否适合缓存行?)访问一行中的 2 个元素可能会受到缓存惩罚,具体取决于它们在缓存中各自的“距离”线。尽可能让您的结构适合缓存行将是有益的。
【解决方案2】:

我的问题是,与 L1 或任何其他布局相比,布局 L2 是否存在缺点?

有时您需要以不同的顺序排列成员。原因可能包括:

  • 该结构是通信协议的一部分,必须通过网络或其他通信设备逐字节发送。在这种情况下,结构的布局可能由协议规定,您必须完全匹配它(包括填充或缺少填充)。
  • 该结构是共享初始成员序列的结构族的一部分,因此必须首先放置这些成员。有时这样做是为了允许通过仅对这些初始成员进行操作来以“通用”方式处理结构。
  • 该结构包含一个长度会发生变化的缓冲区(根据程序执行期间出现的需要动态分配)。这样的缓冲区是用一个灵活的数组成员实现的,它必须是结构中的最后一个成员。

此外,成员的排序方式可能会产生附带影响。例如,如果成员 x 碰巧比结构的其他成员更频繁地使用,将它放在前面可能允许编译器使用更简单的地址算术访问它,因为它与结构开头的偏移量将是零。这在编程中很少考虑,但为了完整起见,我提到它。

除了这些考虑之外,您通常可以根据需要随意订购成员。

自然对齐规则natural alignment = sizeof(T) 也总是适用于原始类型吗?

没有。例如,一个八字节的long 可能需要一个、两个、四个或八个字节的对齐方式。

...如果我们按照独立字段大小的降序排列结构,我们可以切断编译器添加的填充。

这不适用于聚合成员(数组、结构和联合)。考虑成员 char x[13]; 的大小为 13 个字节,但只需要对齐一个字节。为了尽量减少填充,请按照对齐要求递减的顺序排列成员,而不是减小大小。

【讨论】:

  • "按对齐要求递减的顺序排列成员,而不是减小大小"。谢谢埃里克。这是有道理的
【解决方案3】:

IMO 从缓存等实现细节中抽象出来(这太宽泛,无法在帖子答案中讨论)没有区别。

不同之处在于如果您将可变大小(或零大小)的对象放在结构的末尾(示例):

typedef struct
{
    size_t size;
    char data[];
}string;

string *create(size_t size)
{
    string *ptr = malloc(sizeof(*ptr) + size);
    if(ptr)
    {
        ptr -> size = size;
    }
    return ptr;
}

如果结构将存储一些接收到的二进制数据(例如通信数据包头),则当然需要逻辑成员顺序(和潜在的打包)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-18
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多