内存对齐和填充 - 32位和64位之间的差异

时间:2017-02-13 17:52:29

标签: c gcc memory struct x86

我想了解以下小代码中使用“gcc -m32”和“gcc -m64”编译得到的结果:

#include <stdio.h>
#include <stdlib.h>

int main() {

struct MixedData
{
 char Data1;
 short Data2;
 int Data3;
 char Data4;
};

struct X {
 char c;
 uint64_t x;
};

printf("size of struct MixedData = %zu\n", sizeof(struct MixedData));
printf("size of struct X = %zu\n", sizeof(struct X));
printf("size of uint64_t = %zu\n", sizeof(uint64_t));

return 0;
}

使用“gcc -m32”,输出为:

size of struct MixedData = 12
size of struct X = 12
size of uint64_t = 8

struct X的大小是否等于12,因为编译器设置了以下填充?

struct X {
 char c;     // 1 byte
 char d[3];  // 3 bytes
 uint64_t x; // 8 bytes
};

如果是这种情况,32位编译的单个字的大小是多少(4个字节?)?如果它等于4个字节,这将是一致的,因为12是4的倍数。

现在关于带有“MixedData”汇编的gcc -m32的大小,我得到“size of struct MixedData = 12”。我不明白这个值,因为我看到结构的总大小必须是此结构中最大大小属性的倍数。例如,此处为structure MixedData,最大属性为int Data3 sizeof(Data3) = 4 bytes;为什么我们不用以下填充:

struct MixedData
{
 char Data1;        // 1 byte
 char Datatemp1[3]; // 3 bytes
 short Data2;       // 2 bytes
 short Data2temp;   // 2 bytes
 int Data3;         // 4 bytes 
 char Data4;        // 1 byte
 char Data4temp[3]  // 3 bytes
};

所以struct MixedData的总大小将等于16 bytes,而不是12 bytes,就像我得到的那样。

有人能看出这两种解释有什么不对吗?

类似的问题是关于“gcc -m64”汇编;输出是:

size of struct MixedData = 12
size of struct X = 16
size of uint64_t = 8

struct X16 bytes)的大小似乎是一致的,因为我认为64位模式的编译器设置了以下填充:

struct X {
 char c;     // 1 byte
 char d[7];  // 7 bytes
 uint64_t x; // 8 bytes
};

但我不明白struct MixedData12 bytes)的价值。实际上,我不知道编译器在这种情况下如何设置填充,因为12不是64位模式下的存储器字的倍数(假设这个等于8 bytes)。你能告诉我在最后一种情况下(gcc -m64)“struct MixedData”生成的填充吗?

1 个答案:

答案 0 :(得分:0)

这是好奇心

struct
{
 char Data1;
 short Data2;
 int Data3;
 char Data4;
} x;

unsigned fun ( void )
{
    x.Data1=1;
    x.Data2=2;
    x.Data3=3;
    x.Data4=4;
    return(sizeof(x));
}

编译然后反汇编

64

0000000000000000 <fun>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   c6 05 00 00 00 00 01    movb   $0x1,0x0(%rip)        # b <fun+0xb>
   b:   66 c7 05 00 00 00 00    movw   $0x2,0x0(%rip)        # 14 <fun+0x14>
  12:   02 00 
  14:   c7 05 00 00 00 00 03    movl   $0x3,0x0(%rip)        # 1e <fun+0x1e>
  1b:   00 00 00 
  1e:   c6 05 00 00 00 00 04    movb   $0x4,0x0(%rip)        # 25 <fun+0x25>
  25:   b8 0c 00 00 00          mov    $0xc,%eax
  2a:   5d                      pop    %rbp
  2b:   c3                      retq   

32

00000000 <fun>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   c6 05 00 00 00 00 01    movb   $0x1,0x0
   a:   66 c7 05 02 00 00 00    movw   $0x2,0x2
  11:   02 00 
  13:   c7 05 04 00 00 00 03    movl   $0x3,0x4
  1a:   00 00 00 
  1d:   c6 05 08 00 00 00 04    movb   $0x4,0x8
  24:   b8 0c 00 00 00          mov    $0xc,%eax
  29:   5d                      pop    %ebp
  2a:   c3                      ret    

理解m32和m64可能描述得很差,一个基本上是32位处理器,32位寄存器(ebx,eax,ax,但不是rbx,rax)和另一个64位处理器和64位寄存器( RBX,EBX,BX,BH,BL)

结构体的大小或构造与所选指令集之间不必存在联系。

这里有趣的是struct 1 + 2 + 4 + 1 = 8的大小,所以他们可以用8个字节完成它。现在他们可能想要int对齐,所以它会填充一个字节,也许他们希望整个事物在32位边界上对齐添加3个更多,这可能就是发生了什么。 32位代码确实使这一点变得清晰,它们不仅对齐了int而且还对齐了short。因此,它们在Data1和Data2之间填充,以便在16位边界上对齐Data2,然后使Data3在32位边界上对齐,而Data3是一个字节,因此无法对齐。填充结尾以对齐.data中的下一个内容。

64位代码看起来很糟糕,也许他们希望链接器修补那个代码。

00000000004004d6 <fun>:
  4004d6:   55                      push   %rbp
  4004d7:   48 89 e5                mov    %rsp,%rbp
  4004da:   c6 05 57 0b 20 00 01    movb   $0x1,0x200b57(%rip)        # 601038 <x>
  4004e1:   66 c7 05 50 0b 20 00    movw   $0x2,0x200b50(%rip)        # 60103a <x+0x2>
  4004e8:   02 00 
  4004ea:   c7 05 48 0b 20 00 03    movl   $0x3,0x200b48(%rip)        # 60103c <x+0x4>
  4004f1:   00 00 00 
  4004f4:   c6 05 45 0b 20 00 04    movb   $0x4,0x200b45(%rip)        # 601040 <x+0x8>
  4004fb:   b8 0c 00 00 00          mov    $0xc,%eax
  400500:   5d                      pop    %rbp
  400501:   c3                      retq   
啊,我明白这就是他们正在做的事情。这就是他们对Data2和Data3进行了对齐。我想我应该让它为整个结构生成地址......

struct
{
 char Data1;
 short Data2;
 int Data3;
 char Data4;
} x;

unsigned fun ( void )
{
    unsigned long long z;
    z=(unsigned long long)&x;
    x.Data1=1;
    x.Data2=2;
    x.Data3=3;
    x.Data4=4;
    return(sizeof(x));
}

int main ( void )
{
    fun();
}
制造

00000000004004d6 <fun>:
  4004d6:   55                      push   %rbp
  4004d7:   48 89 e5                mov    %rsp,%rbp
  4004da:   48 c7 45 f8 38 10 60    movq   $0x601038,-0x8(%rbp)
  4004e1:   00 
  4004e2:   c6 05 4f 0b 20 00 01    movb   $0x1,0x200b4f(%rip)        # 601038 <x>
  4004e9:   66 c7 05 48 0b 20 00    movw   $0x2,0x200b48(%rip)        # 60103a <x+0x2>
  4004f0:   02 00 
  4004f2:   c7 05 40 0b 20 00 03    movl   $0x3,0x200b40(%rip)        # 60103c <x+0x4>
  4004f9:   00 00 00 
  4004fc:   c6 05 3d 0b 20 00 04    movb   $0x4,0x200b3d(%rip)        # 601040 <x+0x8>
  400503:   b8 0c 00 00 00          mov    $0xc,%eax
  400508:   5d                      pop    %rbp
  400509:   c3                      retq   

确认基地址0x60138。

结构与指令集无关。改为这个

struct
{
 char Data1;
 short Data2;
 int Data3;
 char Data4;
} __attribute__((packed)) x;

unsigned fun ( void )
{
    unsigned long long z;
    z=(unsigned long long)&x;
    x.Data1=1;
    x.Data2=2;
    x.Data3=3;
    x.Data4=4;
    return(sizeof(x));
}

int main ( void )
{
    fun();
}

我们得到了这个

00000000004004d6 <fun>:
  4004d6:   55                      push   %rbp
  4004d7:   48 89 e5                mov    %rsp,%rbp
  4004da:   48 c7 45 f8 38 10 60    movq   $0x601038,-0x8(%rbp)
  4004e1:   00 
  4004e2:   c6 05 4f 0b 20 00 01    movb   $0x1,0x200b4f(%rip)        # 601038 <x>
  4004e9:   66 c7 05 47 0b 20 00    movw   $0x2,0x200b47(%rip)        # 601039 <x+0x1>
  4004f0:   02 00 
  4004f2:   c7 05 3f 0b 20 00 03    movl   $0x3,0x200b3f(%rip)        # 60103b <x+0x3>
  4004f9:   00 00 00 
  4004fc:   c6 05 3c 0b 20 00 04    movb   $0x4,0x200b3c(%rip)        # 60103f <x+0x7>
  400503:   b8 08 00 00 00          mov    $0x8,%eax
  400508:   5d                      pop    %rbp
  400509:   c3                      retq   

结构的大小现在是8个字节,它们生成了未对齐的访问。