C(++) 结构体强制额外填充

10

我看到了无数这样的问题:“我不喜欢填充,怎么关闭它?”但是我还没有找到关于强制编译器提供额外填充的任何信息。

我现在遇到的具体情况是:

struct particle{
  vect2 s;
  vect2 v;
  int rX;
  int rY;
  double mass;
  int boxNum;
};

如何让结构体 struct {double x; double y;} vect2 对齐到16字节边界,以便使用SSE2?添加额外的int后,结构体大小从48字节变为56字节,导致段错误。有没有编译器指令能够自动将结构体对齐到16字节或者在其内部填充字节使其成为16字节的倍数?我知道可以手动实现(例如追加一个额外的 char[12]),但我更想告诉编译器(最好是GCC或ICC兼容版本),以后如果更改了结构体就不用再手动修改。


7
在C++11中,现在有alignas来实现这个目的。 - ildjarn
我认为GCC尚未实现这个功能。 - chris
2
然后请参见https://dev59.com/xVnUa4cB1Zd3GeqPfvk- - Ben Voigt
你能否将你的 vect2__m128 进行联合吗?这样可以指示gcc在堆栈上将你的结构体对齐到16字节。 - ecatmur
我最初将其作为一个与__m128d联合使用的,但由于被告知这样做会导致生成的代码质量很差,应该改用_mm_load_pd(),因此我将其删除了。不过,仅仅使用它来对齐可能是可行的。 - zebediah49
显示剩余2条评论
5个回答

9

你可以嵌套两个结构体来自动填充它,而不需要自己跟踪大小。

struct particle
{
    // ...
};

{
    particle p;
    char padding[16-(sizeof(particle)%16)];
};

很遗憾,如果结构体已经是16的倍数,则此版本会增加16个字节。这是不可避免的,因为标准不允许长度为零的数组。

一些编译器作为扩展允许长度为零的数组,在这种情况下,您可以使用以下方法代替:

struct particle_wrapper
{
    particle p;
    char padding[sizeof(particle)%16 ? 16-(sizeof(particle)%16) : 0];
};

如果结构体已经是16的倍数,则此版本不会添加任何填充字节。


1
你可以通过执行char padding[ (sizeof(particle) + 15) & ~15 ]来避免添加16字节。 - Pedro
1
这是可以工作的,但前提是16是2的幂。如果你将~15转成二进制,则得到结果为1..10000,即它过滤掉了最后四位,仅保留16的倍数。由于这只截取了该数字的最小16倍数,所以我们必须先加上15,这样才能获得下一个最高的16倍数。 - Pedro
抱歉,我忘记减去struct的实际大小。正确的声明应该是char padding[((sizeof(particle) + 15) & ~15) - sizeof(particle)]。无论如何,在gcc中,在结构体声明的末尾添加 __attribute__((aligned(16))) 可以自动完成这个任务。 - Pedro
@Pedro,有了这个更正,你和我终于达成了一致。现在唯一的问题是当大小已经是16的倍数并且你正在尝试创建padding[0]时,即使编译器允许它,它可能会保留一个额外的字节。 - Mark Ransom
很遗憾,padding[0] 不合法;数组必须至少有一个元素。 - ecatmur
显示剩余3条评论

9
gcc中,您可以使用__attribute__((aligned(...)))来对任意类型和变量进行对齐。以您的例子为例,这将是:
struct particle{
  vect2 s;
  vect2 v;
  int rX;
  int rY;
  double mass;
  int boxNum;
} __attribute__((aligned (16)));

这将自动填充结构体,以便其中的数组正确对齐。

3

我想添加自己的答案,以便有人寻找解决方案。Mark的解决方案很不错,可以实现自动化要求,但这不是我最终选择的方案。我想避免这种情况,这就是为什么我提出问题,但有一个“微不足道”的解决方案:

struct particle{
  vect2 s;
  vect2 v;
  int rX;
  int rY;
  double mass;
  int boxNum;
  char padding[12];
};

通过手动检查struct的当前大小,您可以添加适当数量的字符(或其他内容,但是char可以以字节为单位),使其达到正确的大小。即使需要每次更新结构时进行更新,但这显示了最佳性能以及简单性。在这种情况下可以接受,尽管如果您有一个根据选项可以更改大小的结构,则会出现问题。
请注意,我的struct为56个字节,我添加了12个字节使其变为64个字节。由于结尾的int已经被填充了4个字节以达到8字节边界,因此这种计算并不正确;在此之前,该 struct实际上只有52个字节。 只需添加5个char即可使struct长度为57个字节,这将被填充到64个字节,但这不是一个好的解决方案,这就是为什么我使用12来确保它完全符合要求的原因。

6
考虑到性能要求,这样做似乎是明智的;为了您的同事和未来的维护者(包括您未来的自己),请注释修复内容并添加编译时断言,确保struct大小是16字节的倍数。 - ecatmur
2
你为什么不想让编译器来处理这个问题呢?比如使用__attribute__((aligned(16))) - Pedro

1

新的C++11规范也有一个新特性,尽管我不认为许多供应商已经实现了它们。

你可以尝试使用pack pragma,尽管它不受规范支持。不过GCC和MS都支持它。

这将使结构体在1字节边界上对齐,但您可以将数字更改为任何您想要的值。

#pragma pack(push,1)
// ...
#pragma pack(pop)

更新:

显然,上述方法不起作用,因为它只会缩小填充,而不会扩展它。很遗憾,今天下午我没有测试环境。

也许使用匿名联合可能会起作用。我知道它会扩展到最大的大小,但我不知道是否有其他关于对齐的保证。

template<typename T, size_t padding_size>
  struct padded_field {
    union {
      T value;
      uint8_t padding[padding_size];
    };
  };

3
我尝试过了,当设置为4时,它会压缩到52个字节。但是当设置为16时,它仍然是56个字节,因此我认为它不会扩展填充,只会强制更紧密的打包。 - zebediah49
1
我不熟悉gcc的实现,但根据Visual C++文档,“成员的对齐将在一个边界上,这个边界是n的倍数或成员大小的倍数,取决于哪个更小。” - James McNellis

1

没有测试过,但这可能有效:

#include <xmmintrin.h>

struct particle{
  union {
    vect2 s;
    __m128 s_for_alignment;
  };
  union {
    vect2 v;
    __m128 v_for_alignment;
  };
  ...
};

我知道gcc以前在正确对齐__m128方面存在问题,但现在应该已经解决了。


在这种情况下,我可能会选择 union vect2 { __m128d s; struct{ double x; double y;};};,但是是的,那可能是正确的方法。 - zebediah49
测试表明,这种方法的速度比手动填充慢大约10%;我不是完全确定原因。 - zebediah49
疯了。生成的汇编代码有什么不同吗? - ecatmur
阅读 ASM 不是我的强项,但当我打开 union 时(还填充了结构体,以便我只对一处更改进行基准测试),Callgrind 记录的指令数从 11G 增加到 13G。比较其中一个缓慢的部分,我注意到循环中有 20 条指令而不是 10 条:它从 movsd{3}, addsd{2}, movsd{2}, ucomisd, jbe 变为 mov,movsd,mov{8},addsd,mov{3},movsd,addsd,movsd,ucomisd,movsdjbe。大多数 mov 指令似乎都是在 %rdx%rax0x??(%rsp) 之间移动东西。 - zebediah49
@zebediah49 给我感觉代码没有被正确地优化。我知道这是很久以前的事情了,但你记得当时是否向 GCC 传递了足够强的优化标志(比如 -O3 或者至少 -O2)吗?我想最近版本的编译器如果给予足够的自由去优化,不会有那么严重的效率问题。 - mtraceur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接