C/C++ 中结构布局的最佳实践

Best practice for struct layout in C/C++

我已经阅读了一些文章和 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)

基本类型总是正确的?

只有当您 运行 在内存受限的系统上(您想限制每个元素的大小)时,您才应该考虑 alignement/structure 大小。这仅对 systems/designs 有意义,您知道您将存储大量此类结构,而使用的存储空间 space 是一个问题。另一方面,从逻辑上对结构的内容进行分组可以使您的代码更加清晰。我总是赞成清晰度而不是性能,尤其是在早期设计阶段。如果您没有与性能相关的限制,甚至更多。

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

My question here is, are there scenarios where layout L2 has downsides when compared to L1 or any other layout ?

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

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

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

抛开这些考虑,您通常可以随意订购会员。

Also is the natural alignment rule natural alignment = sizeof(T) always true for primitive types ?

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

… we can cutoff compiler added padding if we layout the structs in the decreasing order of size of independent fields.

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

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

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

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;
}

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