C 结构体大小对齐

14

我希望C结构体的大小是16字节的倍数(16B/32B/48B/..)。无论最终大小是多少,它只需要是16字节的倍数。

如何让编译器实现这一点?


1
你的标题使用了“对齐”这个词,但是你的问题正文谈论的是结构体的大小。你实际想要实现什么?我也很好奇为什么。 - Richard Chambers
这里有一篇现有的文章,提供了使用联合来填充到特定大小的解决方案。然而,该解决方案需要知道结构体的大小结构体大小,但你需要做的是使用字符数组的大小计算,例如(sizeof(struct it)/16+1)*16,而不是一个数字常量。 - Richard Chambers
2
@RichardChambers 的原因是因为我有一个16B对齐的数据块,而这个结构体将位于该块的开头,我仍然希望实际数据部分(在结构体之后)的开头对齐到16B以获得良好的SSE性能。 - Neo Hpcer
@RichardChambers 是的。标题似乎有些误导人。我的担忧是结构体的大小,而不是对齐方式。 - Neo Hpcer
4个回答

23

对于Microsoft Visual C++:

#pragma pack(push, 16)

struct _some_struct
{
     ...
}

#pragma pack(pop)

对于GCC:

struct _some_struct { ... } __attribute__ ((aligned (16)));

示例:

#include <stdio.h>

struct test_t {
    int x;
    int y;
} __attribute__((aligned(16)));

int main()
{
    printf("%lu\n", sizeof(struct test_t));
    return 0;
}

使用gcc -o main main.c编译后,输出将为16。其他编译器也一样。


2
对于gcc attribute aligned,它是否实际上强制结构体大小为16B的倍数?还是仅保证结构体的起始地址对齐到16B? - Neo Hpcer
1
结构体的起始地址和大小都将对齐到16字节。这样做是为了保证结构体数组中元素的正确对齐。 - vharavy
这是不正确的。__attribute(aligned(x)) 宏将在x字节边界上分配结构体,并与结构体的大小无关。http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/Variable-Attributes.html - Sid Sarasvati
4
确实如此(这是暗示)。你需要自己检查。我已经用一个例子更新了我的回答。 - vharavy

7
这完全取决于编译器和其他工具,因为ISO C标准中没有详细说明对齐方式(它指定对齐可能按照编译器的要求进行,但不详细说明如何执行)。
您需要查看特定于实现的内容,了解您的编译器工具链。它可能提供 #pragma pack(或 align 或其他某些东西),您可以将其添加到结构定义中。
它也可能作为语言扩展进行提供。例如,gcc 允许您向定义添加属性,其中之一控制对齐方式:
struct mystruct { int val[7]; } __attribute__ ((aligned (16)));

7
C 结构体的大小取决于结构体成员、它们的类型和数量。实际上,没有标准方法可以强制编译器使结构体成为某个大小的倍数。一些编译器提供了一个 pragma,允许您设置对齐边界,但这实际上是不同的事情。可能有一些编译器会有这样的设置或提供这样的 pragma。
然而,如果您坚持使用这种方法,一种方法是对结构体进行内存分配,并强制将内存分配舍入到下一个 16 字节大小。
所以,如果您有一个像这样的结构体。
struct _simpleStruct {
   int iValueA;
   int iValueB;
};

然后你可以按照以下步骤进行操作。
{
    struct _simpleStruct *pStruct = 0;
    pStruct = malloc ((sizeof(*pStruct)/16 + 1)*16);
    // use the pStruct for whatever
    free(pStruct);
}

这样做的作用是将所需大小提升到下一个16字节大小,就您而言是如此。 但是,内存分配器所做的可能是或可能不是给您一个实际大小为该大小的块。 内存块实际上可能比您的请求要大。

如果您打算对此进行特殊处理,例如说您要将此结构写入文件并且想知道块大小,则需要执行与malloc()中使用的相同计算,而不是使用sizeof()运算符来计算结构的大小。

因此,接下来要做的事情就是编写自己的sizeof()运算符,使用宏,如下:

#define SIZEOF16(x) ((sizeof(x)/16 + 1) * 16)

据我所知,没有可靠的方法可以从指针中获取已分配块的大小。通常,指针将具有一个内存分配块,该块由内存堆管理函数使用,其中包含各种内存管理信息,例如分配的块大小可能实际上比请求的内存量大。但是,此块的格式以及相对于提供的实际内存地址的位置取决于C编译器的运行时。

回答不错,但是如果类型已经是16字节的倍数,那么这个宏将会在其大小上再增加16字节,可能会浪费内存。这里提供一种使用三目运算符消除这种情况的替代方案:#define SIZEOF16(x) ((sizeof(x) & 0x0F) ? (sizeof(x) + (0x10 - (sizeof(x) & 0x0F))) : sizeof(x)) - rsethc
@rsethc 说得好。我想我的担忧在于宏参数的多次使用。大多数人知道不要在宏中使用修改表达式,比如 x++,而且通常所有大写字母的函数看起来都像是宏,所以我想这可能是相当无害的。我想一个 inline 函数也可以做到。 - Richard Chambers
在宏中多次使用是一个合理的问题,尽管 sizeof 在编译时就已经确定了。我无法想象它会像这样被使用:SIZEOF16 = (x++),但如果真的这样使用,我认为可能有一些方法告诉 GCC 只评估一次宏参数(我记不清了)。 - rsethc

4
您可以尝试使用双重结构体,将实际的结构体包装在第二个结构体中,以添加填充:
struct payload {
  int a;    /*Your actual fields. */
  float b;
  char c;
  double d;
};

struct payload_padded {
  struct payload p;
  char   padding[16 * ((sizeof (struct payload) + 15) / 16)];
};

然后您可以使用填充结构进行操作:
struct payload_padded a;

a.p.d = 43.3;

当然,您可以利用结构的第一个成员从结构开始0字节的事实,并把指向`struct payload_padded`的指针视为指向`struct payload`的指针(因为它就是)。
float d_plus_2(const struct payload *p)
{
  return p->d + 2;
}

/* ... */

struct payload_padded b;
const double dp2 = d_plus_2((struct payload *) &b);

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