iOS 底层探索篇 —— 内存字节对齐分析

前言程序员

1、内存对齐规则

1.对齐系数

每一个特定的平台上的编译器都有本身的默认“对齐系数”(也叫对齐模数)。咱们能够经过预编译命令#pragma pack(n),n=一、二、四、八、16 来改变这一系数,其中的n就是要指定的“对齐系数”。咱们iOS编译器Xcode的对齐系数就是8。安全

2.对齐规则

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

2、结构体内存分析

1.不用变量的内存分析

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

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

NSLog(@"myStruct1 - %lu",sizeof(myStruct1));
NSLog(@"myStruct2 - %lu",sizeof(myStruct2));
复制代码
  • 对于一些基本的数据类型所占用的字节大小,你们应该都是很是清楚的了。
  • 经过打印输出的结果能够看到myStruct1 - 16myStruct2 - 24.

分析:bash

Struct1类型 位置 补齐 Struct2类型 位置 补齐
double a [0 - 7] 0 int a [0 - 3] 4
int b [8 - 11] 0 double b [8 - 15] 0
char c [12 - 12] 1 int c [16 - 19] 0
short b [14 - 15] 0 char b [20 - 20]
  • Struct1总体对齐以后:大小为16。
  • Struct2总体对齐以后:大小为24。

2.相同变量的内存分析

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

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

NSLog(@"myStruct1 - %lu",sizeof(myStruct1));
NSLog(@"myStruct2 - %lu",sizeof(myStruct2));
复制代码
  • 经过打印输出的结果能够看到myStruct1 - 16myStruct2 - 24.

分析:post

Struct1类型 位置 补齐 Struct2类型 位置 补齐
double a [0 - 7] 0 int a [0 - 3] 4
int b [8 - 11] 0 double b [8 - 15] 0
char c [12 - 12] 1 char c [16 - 16] 1
short b [14 - 15] 0 short d [18 - 19]
  • Struct1总体对齐以后:大小为16。
  • Struct2总体对齐以后:大小为24。

3.结构体做为变量的内存分析

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

struct Struct2 {
    int a;
    double b;
    char d;
    struct Struct1 myStruct1;
}myStruct2;
NSLog(@"myStruct2 - %lu",sizeof(myStruct2));
复制代码
  • 经过打印输出的结果能够看到myStruct2 - 24.

分析:性能

Struct2 类型 位置 补齐
int a [0 - 3] 4
double b [8 - 15] 0
char c [16 - 16] 7

咱们按照规则来算,成员为结构体的,按照结构体的本身内部数据成员的最大长度的整数倍储存。atom

Struct2 类型 位置 补齐
double a [24 - 31] 0
int b [32 - 35] 0
char c [36 - 36] 1
short d [38 - 39]
  • Struct2总体对齐以后:大小为40。

3、OC类属性内存分析

1.自定义类和属性

@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)));
}
复制代码

输出结果 40 - 48。spa

  • 对象申请的内存空间 <= 系统开辟的内存空间。
  • 对象申请的内存空间是以8字节对齐方式。在objc源码里面是能够获得验证的。
  • 系统开辟内存空间是以16字节对齐方式。在malloc源码里面segregated_size_to_fit()能够看到是以16字节对齐的。

2.lldb调试查看

x/6xg p1意思表明 读取p1对象6段内存地址。调试

(lldb) x/6xg p1
0x600000ce0000: 0x00000001029570d0 0x0000001200006261
0x600000ce0010: 0x0000000102956098 0x00000000000000b4
0x600000ce0020: 0x00000001029560b8 0x0000000000000000
(lldb) po 0x00000001029570d0 & 0x0000000ffffffff8
XDPerson
(lldb) po 0x00000012
18
(lldb) po 0x62
98
(lldb) po 0x61
97
(lldb) po 0x0000000102956098
xiedong
(lldb) po 0x00000000000000b4
180
(lldb) po 0x00000001029560b8
男
复制代码

发现OC里面程序员写的属性的顺序并非内存里面的顺序,与结构体struct仍是有必定的区别。其实这里就是编译器给进行二进制重排产生的效果。code

  • 第一个内存地址是isa,是objc_object这个基类带的数据成员。后面的章节中会有所介绍。

4、内存对齐缘由

  1. 内存对齐是编译器处理的。
  2. CPU读取未对齐的内存时,其性能会大大的下降,此时CPU会进入到异常状态,而且通知程序不能继续进行。
  3. CPU并非以字节为单位来存取数据的,它会把内存当成一块一块的,其块的大小能够是二、四、八、1六、32字节,每次读取都是一个固定的开销,减小内存存取次数提高应用程序的性能。

咱们能够想一下,假设CPU先从0地址读取4字节到寄存器,这个时候内存是对齐的,一次读取4字节。而后在从1地址读取,先读取2字节,再读取2字节,而后再合成到寄存器,这个时候CPU的性能就会相对上一次下降,对整个应用程序的性能一定会产生相应的影响。对象

5、内存对齐在OC中的优势

有时候咱们会思考为何系统开辟的内存大小会大于咱们申请的内存大小呢?按照8字节对齐的方式,申请的内存就可能已经存在多余的了,就拿上面的例子int和两个char就会多了两字节。

  1. 按照8字节对齐方式,对象内部里面的成员内存地址是绝对安全的。
  2. 咱们没法肯定申请多余的字节就在对象与对象之间,有可能会出如今对象内存段的内部某个位置,这个时候就可能会出现两个对象内存段是挨着的状况,没有那么的安全。系统开辟空间采起16字节方式,保证对象的内存空间会更大,对象与对象之间更加的安全。
相关文章
相关标签/搜索