【问题标题】:Why should a struct's size reflect its alignment?为什么结构的大小应该反映它的对齐方式?
【发布时间】:2015-11-13 00:29:26
【问题描述】:

根据Wikipedia

最后一个成员被填充所需的字节数,因此结构的总大小应该是任何结构成员的最大对齐方式的倍数

在我的理解中,它的意思如下:

struct A {
  char *p;     // 8 bytes
  char c;      // 1 byte
};
struct B {
  struct A a;  // 16 bytes
  char d;      // 1 bytes
};

struct A 的大小为 16 字节,struct B 的大小为 24 字节。

常见的解释是A 的数组应该在数组的地址加上索引乘以A 的大小来访问其元素。

但我不明白为什么会这样。为什么我们不能说 A 的大小为 9 和 B 的大小为 10(均 8 字节对齐),并在索引数组时使用特殊的“数组存储”大小?

当然,我们仍然会以与它们的对齐方式兼容的方式将这些类型存储在数组中(使用 16 个字节来存储每个 B 元素)。然后,我们只需考虑它们的对齐方式来计算元素地址,而不是单独考虑它们的大小(编译器可以静态地做到这一点)。

例如,我们可以在 B 的 1Kb 字节数组中存储 64 个对象,而不仅仅是 42 个。

【问题讨论】:

  • 优化通常,在某些平台上读取 CPU 本机字边界上的数据可能比读取未对齐的数据更快(例如,如果数据未对齐,则必须读取两个单词并屏蔽想要的单词)。
  • 换一种说法,这是一个软件规范,不是为了软件的直接利益。相反,它是与硬件合作,在大多数情况下,这种对齐方式要么更好,要么在某些极端情况下,如果在没有这种对齐的情况下使用会崩溃(抛出软件必须处理的硬件异常或崩溃)。
  • 请注意,我从不谈论不尊重对齐约束。
  • 如果不能保证sizeof(A)的值是相同的,无论对象是在一个数组中,在另一个struct中,还是它本身,我可以看到指针运算和分配内存时的问题使用malloc

标签: c size padding memory-alignment


【解决方案1】:

在 C 的每个翻译单元中,sizeof(T) 是相同的,与 T 的上下文无关。您的提议将为sizeof(T) 引入至少两个值:一个用于T 的数组,另一个用于T 的单个对象。这基本上将上下文相关性引入了sizeof 运算符。它与 C 处理指针、数组和对象地址的方式不兼容。

考虑以下几点:

void zero_A(struct A *a) { memset(a,0,sizeof(*a)); }

/* ... */
struct A single;
struct A several[3];
struct B b;

b.d = 3;
zero_A(&b.a);
zero_A(&single);
zero_A(several+1);

根据您的建议,zero_A 必须知道它传递的指针是指向数组上下文中的struct A(其中sizeof(*a) == 16)还是指向数组上下文之外的struct A(其中sizeof(*a) == 9) .标准 C 不支持这一点。如果编译器猜错了,或者信息丢失了(例如:在通过volatile struct A * 的往返行程中),那么zero_A(&single) 将调用未定义的行为(通过写入超过single 的边界),并且zero_A(&b.a)将覆盖 b.d 并调用未定义的行为。

将结构紧密打包到数组中是一个相对不常见的要求,并且将上下文依赖添加到 sizeof 会给语言、它的库和 ABI 带来很多复杂性。有时您需要这样做,而 C 为您提供了所需的工具:memcpy 和联合。

【讨论】:

  • 在我的想法中,sizeof operator 将始终返回实际大小(而不是填充大小),因此它不需要是上下文的。而且 AFAIK 您不需要将填充位归零(C 标准对此有什么说明吗?),因此您可以在 zero_A 中安全地使用 memset。唯一真正需要做的改变是向A* 指针添加1:它需要将填充大小 添加到指针值(称为psizeof(T))。现在我意识到这对于迭代结构中相同类型的一堆连续字段是行不通的。也许 C 标准要求这是可能的?
猜你喜欢
  • 2016-04-15
  • 2017-04-07
  • 1970-01-01
  • 2016-08-05
  • 1970-01-01
  • 2016-01-18
  • 2011-06-05
  • 2012-06-23
  • 1970-01-01
相关资源
最近更新 更多