内存字节对齐

我们可以先看看下面的结构体,观察一下结构体的内存分配情况:

struct Struct1 {
    int a;
    double b;
    int c;
    char d;
    short e;
}myStruct1;

struct Struct2 {
    int a;
    double b;
    char d;
    int c;
    short e;
}myStruct2;

NSLog(@"%lu - %lu - %lu",sizeof(myStruct1),sizeof(myStruct2));
打印结果 24 - 32

struct Struct3 {
    double b;
    char d;
    int c;
    short e;
    struct  Struct2 myStruct2;
}myStruct3;
NSLog(@"%lu",sizeof(myStruct3));
打印结果 56

struct Struct4 {
    int c;
}myStruct4;

struct Struct5 {
    double b;
    char d;
    int c;
    short e;
    struct  Struct4 myStruct4;
}myStruct5;
NSLog(@"%lu",sizeof(myStruct5));
打印结果 24

比较Struct1 和 Struct2 属性是相同的,但是属性的位置有一定的区别。这就是内存对齐的现象。
比较Struct3 和 Struct5的区别,Struct2的属性里面的最大是8,Struct4里面最大是4

内存对齐的规则

每个特定的平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。我们可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是要指定的“对齐系数”。Xcode的对齐系数就是8。
我们在了解内存对齐之前,先看看内存对齐的原则:

  1. 数据成员对齐规则:(Struct 或 union 的数据成员)第一个数据成员放在偏移为0的位置,以后每个成员的偏移为 min(对齐系数,自身长度)的整数倍,不够整数倍的补齐。
  2. 数据成员为结构体:该数据成员的自身长度为其最大长度的整数倍开始存储
  3. 整体对齐规则:数据成员按照上述规则对齐之后,其本身也要对齐,
    对齐原则是min(对其系数,成员最大长度)的整数倍。

通过这个规则我们对Struct5做一下简单的描述
double b 8字节 存储区间 [0-7];
char d 1字节 存储区间[8-9];
int c 4字节 还有[10-15]存储段 保持4的倍数 存储区间[11-16];
short e 2字节 存储区间[17-18];
接下来 struct Struct4 myStruct4;
int c 4字节 有此仅有一个 需要的4的倍数的位置 存储区间[19-23];
保证整体对齐规则 min(对其系数,成员最大长度)为8的整数倍 即24字节

内存对齐原因

内存对齐是编译器帮我们处理的。但一个程序要求CPU读取未对齐的数据时,CPU会进入异常处理状态并且通知程序不能继续执行。因为未对齐的数据,会大大的降低CPU的性能。
CPU并不是以字节为单位来存取数据的,它会把内存当成一块一块的来,块的大小可以是2、4、8、16、32字节,每次读取都是一个固定的开销,减少内存存取次数将提升程序的性能。
我们可以假设 CPU先从地址0 读取4字节到寄存器,这时候读取的是对齐地址的数据,但从地址1读取的时候是非对齐的数据,就可能需要读取几次才能完成,然后在合成之后到寄存器。

OC内存对齐

@interface XDPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic, copy) NSString *sex;

@property (nonatomic) char ch1;
@property (nonatomic) char ch2;

@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
   
    XDPerson *p1 = [XDPerson alloc];
    p1.name = @"xiedong";
    p1.age = 18;
    p1.height = 180;
    p1.sex = @"男";
    p1.ch1 = 'a';
    p1.ch2 = 'b';
    
   NSLog(@"%lu - %lu",class_getInstanceSize([p1 class]),malloc_size((__bridge const void *)(p1)));
}
x/6gx p1打印结果
(lldb) x/6xg p1
0x600000ce0000: 0x00000001029570d0 0x0000001200006261
0x600000ce0010: 0x0000000102956098 0x00000000000000b4
0x600000ce0020: 0x00000001029560b8 0x0000000000000000
(lldb) po 0x00000001029570d0
XDPerson
(lldb) po 0x00000012
18
(lldb) po 0x62
98
(lldb) po 0x61
97
(lldb) po 0x0000000102956098
xiedong
(lldb) po 0x00000000000000b4
180
(lldb) po 0x00000001029560b8
男

NSLog打印的结果 40-48
  1. 可以猜到objc帮我们的对象属性做了优化处理,把size大小比较的小的 int char 组合在了一起.
  2. 然后会发现对象申请的内存空间<=系统开辟的内存空间.
    通过alloc的流程 我们会在objc源码里面看对齐方式是以8字节对齐的。
    通过calloc的流程 我们可以在malloc的源码的segregated_size_to_fit()函数里面可以看到对齐方式是以16字节对齐的。

我们可以发现 对象内部的属性有自己的内存空间是保证安全的,那么对象与对象之间是怎么保证安全的呢?

XDPerson对象内部的属性+isa占用的内存空间是28+8=36, 实际申请的内存是40 。
那么多余申请的4字节是否可以保证对象与对象之间的安全(或者说这多余的4字节是两个对象地址之间空出来的那一段呢)
结果并不是的,这多余的4字节可能是在这个对象内存空间的某个位置,并不一定在最后面。
所以系统为了保证更加的安全,以16字节对齐的方式开辟对象的内存空间,保证对象的内存空间更大,对象与对象之间更加安全。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。