C 内存对齐和填充—;32位和64位之间的差异
我想了解对以下小代码进行“C 内存对齐和填充—;32位和64位之间的差异,c,gcc,memory,struct,x86,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
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;
}
由于编译器设置了以下填充,结构X的大小是否等于12
struct X {
char c; // 1 byte
char d[3]; // 3 bytes
uint64_t x; // 8 bytes
};
如果是这种情况,那么一个32位编译(4字节)的单词的大小是多少?如果它等于4字节,这将是一致的,因为12是4的倍数
现在,关于带“gcc-m32
”编译的MixedData的大小,我得到了“struct MixedData的大小=12
”。我不理解这个值,因为我看到一个结构的总大小必须是这个结构中最大大小属性的倍数。例如,这里的结构MixedData
,最大的属性是intdata3
,其中sizeof(Data3)=4个字节;我们为什么不使用以下填充:
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 X {
char c; // 1 byte
char d[7]; // 7 bytes
uint64_t x; // 8 bytes
};
因此,struct MixedData
的总大小将等于16字节
,而不是像我得到的那样12字节
有人能看出这两种解释有什么不对吗
类似的问题是关于“gcc-m64
”编译;输出为:
size of struct MixedData = 12
size of struct X = 16
size of uint64_t = 8
struct X
(16字节
)的大小似乎是一致的,因为我认为64位模式下的编译器设置了以下填充:
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 X {
char c; // 1 byte
char d[7]; // 7 bytes
uint64_t x; // 8 bytes
};
但是我不理解structmixeddata
(12字节
)的值。事实上,我不知道编译器在这种情况下是如何设置填充的,因为在64位模式下,12不是内存字的倍数(假设这一个等于8字节
)。您能告诉我在最后一种情况下“gcc-m64
”生成的填充(对于struct MixedData
)吗?这是一个好奇的问题
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
生产
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
我们得到了这个
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
0000000000 4004D6:
4004d6:55%推送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
4004e9:66 c7 05 47 0b 20 00 movw$0x2,0x200b47(%rip)#601039
4004f0:02 00
4004f2:c7 05 3f 0b 20 00 03 movl$0x3,0x200b3f(%rip)#60103b
4004f9:00
4004fc:c6 05 3c 0b 20 00 04 movb$0x4,0x200b3c(%rip)#60103f
400503:b8 08 00 mov$0x8,%eax
400508:5d pop%rbp
400509:c3 retq
结构的大小现在是8字节,它们生成了未对齐的访问。short Data2temp[2];//2个字节实际上是4个字节。反汇编显示了什么?我假设这是您询问的x86?您从何处获得此规则结构的大小必须是最大项的倍数?我从intel i7 x86_64编译代码段。关于规则,我认为结构的大小必须是最大项的倍数,因为如果我有一个struct数组,元素将更直接可访问,不是吗?那么初始混合数据将是16或32字节,每个元素为4或8字节。但事实并非如此。我几乎从不拆解或检查x86,我通常坚持使用arm,所以很长的mov指令并不是我所期望的。我故意没有进行优化,因为所有这些都是死代码,可能会消失,需要看到它们实际生成代码来与结构对话。因此,如果这是优化的,而不是死代码,可能会选择其他指令。不仅32位访问(movl)在32位边界上对齐(未压缩),而且16位访问(movw)也在至少16位边界上对齐)。如果您将最后一个元素更改为short而不是int,则应该保持结构的大小不变(12),但更改替换movb的movw的偏移量,同时对齐该访问(在16位边界上)。感谢您的回答,但是我正在做困惑:您说什么“他们正在尝试对齐16位边界上的16位访问和32位边界上的32位访问”?您的意思是编译器试图将结构的总大小设置为4字节(使用gcc-m32)或8字节(使用gcc-m64)的倍数?Regard将8字节结构转换为12。偏移量0数据1,偏移量1跳过以填充数据2,偏移量2数据2,偏移量3数据2的一部分。偏移量4数据3。偏移量处的字节似乎用于对齐数据2。-m32和-m64与结构的形成方式无关
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