结构体中的填充

7

我知道结构体中有填充这篇文章的例子

 struct A   -->8 bytes
 {
    char c;
    char d;
 //2 padding here
    int i;
 };
 struct B  -->12 bytes
 {
     char c;
 //3 padding here
    int i;
    char d;
 //3 padding here
 };

现在,我不理解以下示例:
 typedef struct {  -->**shouldn't it be 12 bytes**
    int a;
    char *str;
 } TestS;

 TestS s;

int main(int argc, char *argv[]) {

   printf("An int is %lu bytes\n", sizeof( int )); -->4
   printf("A Char * is %lu bytes\n", sizeof( char *)); -->8
   printf("A double is %lu bytes\n", sizeof( double )); -->8

   printf("A struct is %lu bytes\n", sizeof s); -->why 16?

   return 0;

 }

起初我认为可能是对齐到8*N字节(因为我使用的是ubuntu-64),所以我尝试了更多的结构体。

  typedef struct {
   int i;
   char *str;
  } stru_12;


  typedef struct {
    int i;
    char *str;
    char c;
  } stru_13;

 typedef struct {
    int i;
    char str[7];
 } stru_11;

 typedef struct {
   char *str;
   double d;
 } stru_16;

  stru_12 test12;
  stru_13 test13;
  stru_11 test11;
  stru_16 test16;

int main (int argc, char *argv[]) {
    printf("A test12 is %lu bytes, address is %p\n", sizeof test12, &test12);
    printf("A test13 is %lu bytes, address is %p\n", sizeof test13, &test13);
    printf("A test11 is %lu bytes, address is %p\n", sizeof test11, &test11);
    printf("A test16 is %lu bytes, address is %p\n", sizeof test16, &test16);
}

结果:

test12是16字节,地址为0x601060

test13是24字节,地址为0x601090

test11是12字节,地址为0x601080

test16是16字节,地址为0x601070

很抱歉这么长。

我的问题是:

  • 为什么test12(int + char *)是16字节,而test13(int + char * + char)是24字节?(似乎偏爱 8 * N ,但允许12字节)

  • 为什么结构的地址差异是16个寻址单元(更多填充?)?

供您参考:

缓存对齐:64

地址大小:36位物理,48位虚拟

Ubuntu 14.04.1 LTS x86_64


1
这段代码非常杂乱无章。你能否删除所有的typedef和变量,改用sizeof(struct stru_12)等方式?这样可以减少视觉干扰。 - Kerrek SB
谢谢您的建议,但我需要地址。有什么解决办法吗? - Tony
1
24 == 8*3。8 是对齐单位,而不是12。地址之间的差异是没有意义的,除非它们是同一数组元素的地址。 - n. m.
1
@Tony,通常每个成员应该适当对齐,这样整个结构体对象才能在数组中使用。 - Eric Z
@Tony:地址相对无关紧要,所以我建议你不必在意它,但如果你想保留它,那也没问题。这是你的决定。(当然也是你的问题!) - Kerrek SB
2个回答

4
第二个问题是实现定义(实际上,第一个也是,但我将向您展示为什么您无论如何都会得到您获得的间距)。您的平台显然是64位,因此您的数据指针也是如此(64位)。有了这个前提,我们来看一下结构体。

stru_12

typedef struct 
{
   int i;
   char *str;
} stru_12;

这样对齐可以确保str总是在8字节边界上,即使在连续的序列(数组)中也是如此。为了做到这一点,在istr之间引入了4个字节的填充。

0x0000 i    - length=4
0x0004 pad  - length=4
0x0008 ptr  - length=8
======================
Total               16

这些的数组始终会在8字节边界上具有ptr,前提是该数组从该位置开始(它将会这样)。由于在istr之间添加填充也使结构大小成为8的倍数,因此除此之外不需要额外的填充。


stru_13

现在,考虑如何使用以下内容实现同样的效果:

typedef struct 
{
    int i;
    char *str;
    char c;
} stru_13;

相同的填充将应用于str之间,以再次将str放置在8字节边界上,但c的添加使事情变得复杂。为了实现指针始终驻留在8字节边界上(包括这些结构的序列/数组),结构需要尾部填充,但需要多少呢?嗯,我希望显而易见的是,整个结构大小需要是8的倍数,以确保任何嵌入式指针(它们也是8的倍数)被正确对齐。在这种情况下,添加7个字节的尾部填充,使大小达到24字节:
0x0000 i    - length=4
0x0004 pad  - length=4
0x0008 ptr  - length=8
0x0010 c    - length=1
0x0011 pad  - length=7
======================
Total               24

stru_13(第二部分)

尝试这个。你认为我们之前有的相同字段,但是以不同的顺序排序,会产生什么结果:

typedef struct 
{
    char *str;
    int i;
    char c;
} stru_13;

我们知道想要在8字节边界上使用str,在4字节边界上使用i,而对于c(总是作为伴娘),我们其实并不太关心:

0x0000 ptr  - length=8
0x0008 i    - length=4
0x000c c    - length=1
0x000d pad  - length=3
======================
Total               16

运行测试程序,你会看到它与我们上面展示的一样。它被压缩为16字节。我们所做的只是将顺序改为更加友好的空间布局,仍然支持我们的要求,并将默认表示减少了8个字节(相对于之前布局的原始结构的三分之一)。说这是从所有这些中带走的重要事情是低估了它。


2
@martin stru_11没有指针或双精度浮点数,所以8字节边界被抛弃了,但是对于地址i(一个32位的int),理想的访问应该将其放在4字节边界上,并且在序列中保持这种方式。为了实现这一点,在结构尾部添加了一个额外的填充位。结果是12字节长度,而i始终落在4字节边界上(当然,假设它从一个4字节边界开始,这也是肯定的)。尝试将longshort替换为i的类型,并查看发生了什么。 - WhozCraig
@martin 值得注意的是,当您按照结构中成员的最大值最小值的顺序排序时,事情可能会发生很大变化。特别是这一点,值得尝试。 - WhozCraig
那么它必须确保结构体中的每个成员在结构体数组中都可以得到适当的对齐,对吧? - Tony
1
@Tony,重点是你不需要这样做。编译器已经为你完成了。在大多数情况下,你的代码已经很好了。如果你需要将更多的项目放在一页上以更好地利用预取器和缓存行,请花时间进行良好的调整。如果你正在编写新代码,最好养成一个习惯,使它们具有良好的紧凑表示形式,但不要让过早优化的压力破坏一个完美的工作日。如果你这样做,Knuth会从斯坦福一路拍打你。 - WhozCraig
1
如果你指的是有关过早优化是万恶之源的引用,那么不是。这是他在1974年写的一篇论文中提到的,题为《带有GOTO语句的结构化编程》,第268页(这不是讽刺吗)。 - WhozCraig
显示剩余3条评论

3

指针必须正确地对齐才能被CPU使用。

在C/C++中,结构体必须在数组中工作,因此在这方面,结构体的末尾会填充空白。

struct A
{
    char a;
    // 7 bytes of padding
    char *p;
    char b;
    // 7 bytes of padding
};

A array[3];  // the last padding is important to do this

在这样的结构中,必须对p进行对齐,以便处理器可以读取指针而不会生成错误(32位INTEL处理器可以设置为在非对齐数据上没有错误,但那不是一个好主意:它会更慢,而且通常会跳过是错误的漏洞。64位处理器在这个领域有更多的限制。)
所以既然你使用的是64位,指针是8字节,指针之前的对齐必须是8的倍数。
同样,结构体的总大小必须是结构体中最大类型的倍数,这里是8,因此会在末尾填充到下一个8字节。
其实只有两种情况需要关注这个问题:(1)创建一个要保存在文件中的结构体和(2)创建一个将大量分配的结构体。在其他情况下,不必担心这个问题。

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