内存对齐
概念:
百度百科内存对齐:编译器为程序中的每个“数据单元”安排在适当的位置上
为什么要对齐?
一种提高内存访问速度的策略,cpu在访问未对齐的内存需要经过两次内存访问,而经过内存对齐一次就可以了.
举例说明: 32bit的系统一次只能读入32bit的数据, 即4个字节, 如果首地址为0, name读入顺序应该是0-3, 3-7, 8-11 ...... 这样的读入; 在没有对齐的情况下, 一个int变量(4字节)可能分配中2,3,4,5这几个位置上, 如果要获取该变量, 就要进行0-3, 4-7两次读取
像下面的struct:
typedef strutc test{
char a;
int b;
char c;
}
- 未对齐
读取b需要0-3, 4-7两次读取
- 对齐
读取b就可以从4-7一次读取
对齐规则
1. 内存的自然对齐
每一种数据类型都必须放在地址中的整数倍上
- char(1)类型可以放在任何位置
- short(2)只能放在0x2, 1x2, 2x2, 3x2.., 即0, 2, 4, 6 ...的位置上
- int(4)只能放在0x4, 1x4, 2x4, 3x4.., 即0, 4, 8, 12 ...的位置上
- double(8)只能放在0x8, 1x8, 2x8, 3x8.., 即0, 8, 16, 24 ...的位置上
note:以上规则针对的是#pragma pack(16)
和#pragma pack(8)
时适用, 对其系数为1,2,4, 参见下边的说明
代码:
#import "ViewController.h"
typedef struct test{
char a;
int b;
double c;
char d;
}STU;
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
test();
}
int test(void)
{
STU s;
printf("s的大小是 = %d\n",(int)sizeof(STU));
printf("s中a的起始地址是 %p\n",&(s.a));
printf("s中b的起始地址是 %p\n",&(s.b));
printf("s中c的起始地址是 %p\n",&(s.c));
printf("s中d的起始地址是 %p\n",&(s.d));
return 0;
}
@end
输出:
s的大小是 = 24
s中a的起始地址是 0x7ffee06479c8
s中b的起始地址是 0x7ffee06479cc
s中c的起始地址是 0x7ffee06479d0
s中d的起始地址是 0x7ffee06479d8
如果按照规则一, 推断出来的应该是这样的:
四字节对齐, 长度应该是20位
结果其实是这样的:
长度24位, 最后一个char补了7位, 原因就在于规则二
另外修改对齐系数, 的出来的结果也不一样:
pragma pack(16)
s的大小是 = 24
s中a的起始地址是 0x7ffee3e7f9c8
s中b的起始地址是 0x7ffee3e7f9cc
s中c的起始地址是 0x7ffee3e7f9d0
s中d的起始地址是 0x7ffee3e7f9d8
pragma pack(8)
大小是 = 24
s中a的起始地址是 0x7ffee2ab49c8
s中b的起始地址是 0x7ffee2ab49cc
s中c的起始地址是 0x7ffee2ab49d0
s中d的起始地址是 0x7ffee2ab49d8
pragma pack(4)
s的大小是 = 20
s中a的起始地址是 0x7ffee81239c8
s中b的起始地址是 0x7ffee81239cc
s中c的起始地址是 0x7ffee81239d0
s中d的起始地址是 0x7ffee81239d8
pragma pack(2)
s的大小是 = 16
s中a的起始地址是 0x7ffee03f09d0
s中b的起始地址是 0x7ffee03f09d2
s中c的起始地址是 0x7ffee03f09d6
s中d的起始地址是 0x7ffee03f09de
pragma pack(1)
s的大小是 = 14
s中a的起始地址是 0x7ffee2eb49d0
s中b的起始地址是 0x7ffee2eb49d1
s中c的起始地址是 0x7ffee2eb49d5
s中d的起始地址是 0x7ffee2eb49dd
2.补齐规则
在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;
若不是,则补齐为它的整数倍
因为代码中:
typedef struct test{
char a;
int b;
double c;
char d;
}STU;
struct
的成员类型double
是8位长度, 所以struct
的大小必须是8的倍数, 所以最后一位char
要补上7位
3. 包含结构体成员的补齐规则
如果结构体作为成员,则要找到这个结构体中的最大元素,然后从这个最大成员的整数倍地址开始存储
代码:
typedef struct
{
char a;
int b;
double c;
}X;
typedef struct {
char a;
X b;
}Y;
sizeof(X)为16,sizeof(Y)为24, 计算Y的存储长度时,X的最大元素是double
, 所以在存放第二个元素b时的初始位置是在double
型的长度8的整数倍处,而非16的整数倍处,即系统为b所分配的存储空间是第8~23个字节
另外:包含指针类型的情况。只要记住指针本身所占的存储空间是8个字节(64bit系统, 32bit下是4字节)就行了,而不必看它是指向什么类型的指针
typedef struct
{
int* b;
}X;
typedef struct {
char *a;
}Y;
输出:
X的大小是 = 8
Y的大小是 = 8
对齐规则(百科中的描述)
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。