【规则一】数据成员的对齐规则能够理解为min(m, n) 的公式, 其中 m表示当前成员的开始位置, n表示当前成员所须要的位数。若是知足条件 m 整除 n (即 m % n == 0), n 从 m 位置开始存储, 反之继续检查 m+1位置 可否整除 所需的位数n, 直到能够整除, 从而就肯定了当前成员的开始位置。markdown
【规则二】数据成员为结构体:当结构体嵌套告终构体时,做为数据成员的结构体的自身长度做为外部结构体的最大成员的内存大小,好比结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8布局
【规则三】最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的须要补齐。优化
上面的文本,比较枯燥,咱们用图来说解 定义两个结构体 structA 和 structB,咱们就用上面的规则来猜这两个结构体在内存中如何开辟内存?内存占用多少? 任咱们宰割的结构体先准备上atom
struct TestStructA {
double a; // 8 byte
int b; // 4 byte
char c; // 1 byte
short d; // 2 byte
} structA;
struct TestStructB {
double a; // 8 byte
char b; // 1 byte
int c; // 4 byte
short d; // 2 byte
} structB;
复制代码
不清楚OC的基本数据类型在32位和64位上占用的,能够参考下面的表格 spa
先看structA3d
struct TestStructA {
double a; // 8 byte
int b; // 4 byte
char c; // 1 byte
short d; // 2 byte
} structA;
复制代码
一、a :根据规则1,structA的第一个成员a是double类型,偏移从0开始,a占用8个字节,range:【0,8】,以下: 指针
二、b 的内存占用,b是int类型,占4个字节,接着a从地址 8 开始,而8%4==0,七号起始地址能够整除当前要存的b的大小,因此b从8开始,占4个字节,range:【8,12】,以下: code
三、c 是char,须要一个字节,继续b从地址12开始,12%1==0,因此c从12开始,占一个字节,range:【12,13】,以下: orm
四、终于轮到了d,继续c以后从13开始,d是short须要2个字节,可是13%2!=0 , 那就继续下一个位置14开始,14%2!=0, OK,咱们就从14开始存,占2个字节,range:【14,16】,以下: 对象
咱们的c和d之间空了一个位置,也就是13,这个位置系统会自动补0.
根据规则三,当所有安排稳当,结构体的总大小是否是最大成员的整数倍,也就是整个结构体size % 最大成员的size == 0 可成立,若是不能整除,补0直到能够整除最大成员的size。structA很显然,刚恰好 16byte,最大成员a须要8byte,能够整除,因此,第4步就是structA的内存图示。
咱们来验证一下,structA是否是占用了16个字节
再看structB
struct TestStructB {
double a; // 8 byte
char b; // 1 byte
int c; // 4 byte
short d; // 2 byte
} structB;
复制代码
一、a须要8个字节,从0位置开始,和structA同样: 二、b是char类型,须要一个字节,此时应该从位置8继续, 位置8 % b所需的1个字节 == 0,因此,从位置8开始,占一个位置,range:【8,9】
三、c是int,须要4个字节的位置,此时继续从9开始,显然9不能整除4,继续下一个位置10......直到12能够整除4,因此c从位置12开始,range:【12,16】
细心的朋友会发现:structA到位置16就已经结束了,structB的d还没开始计算。 四、d是short,须要2个字节,此时位置继续从16开始,16 % 2 == 0, 因此能够从位置16开始开辟,须要2个字节,因此range:【16,18】
这就结束了?????显然不是!structB总大小18 明显不能整除 最大成员a所需的8个字节,直到补0到位置24,才能够整除8。
最后,structB实际须要24个字节,咱们来验证一下
structA和structB看似同样的写法,所需的内存空间倒是大不相同 因此,了解内存对齐,是开发者的一项基本功。
struct TestStructC {
char *name;
int age;
} structC;
复制代码
先查看结果他所占用的字节:
16个字节!
其实很好理解,char虽然只有一个字节,可是 name是个指针变量 ,指针做为一个变量,须要 8个字节 。name指向的具体内容才是1个字节。 因此在structC里,name指针变量才是成员。
固然了,最后检查一遍:整个结构体的size是否是最大成员size的整数倍!不是,要补全
引伸:对象的大小呢? 咱们在开发时,会定义 LYPerson *person = [LYPerson.alloc init];
本质上,person是一个指针,指向了这个对象在内存中的实际位置。 因此当咱们sizeof(person)
的时候,实际上,是得到这个指针变量的大小!指针须要8个字节!和person的属性内容无关!
定义了这么一个类
@interface LYPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
@property (nonatomic) char c3;
@property (nonatomic) char c4;
@end
复制代码
初始化,先不给属性赋值
LYPerson *person = [LYPerson.alloc init];
复制代码
咱们来看一看person对象的内存分布 经过po person
查看内存地址 而后使用 x/4gx 内存地址
、x/8gx 内存地址
分别查看4个数量和8个数量,每一个分布须要8个字节,每一行有2个分布,也就是16字节。
每一行,如0x600000d72500: 0x00000001099a87a8 0x0000000000000000
表示属性在内存中的地址,一行共16个字节。 咱们打印看一下0x00000001099a87a8
,只有他不为空
0x00000001099a87a8
地址里的值是类名!一行总共16个字节,他就占了8个字节。
一、咱们开始给属性赋值,并打上断点,先给name赋值,并查看内存变化:
LYPerson *person = [LYPerson.alloc init];
person.name = @"一草一晚上一孤城";
person.age = 27;
person.c1 = 'a'; // ascii码 97
person.c2 = 'b'; // ascii码 98
person.c3 = 'A'; // ascii码 65
person.c4 = 'B'; // ascii码 66
复制代码
在0x6000029aad70
出开始的16个字节区域内,多了一个子偏移地址:0x000000010d4e9038
,里面存储值是属性name
的值一草一叶一孤城
。
二、断电继续往下走,给age赋值,并查看内存变化:
有一个内存地址变成了0x1b,它里面存储了age
的内容 27
三、断点继续往下走,给c1赋值: 一样是在
0x6000029aad60
区域,多了一个内存地址0x61,值是c1的‘a’。 四、断点往下走,给c2赋值: 它是紧挨着c1的地址0x61开始存,他是0x62,说明什么,char类型只占1个字节!
五、继续往下走,给c3赋值:
六、断点继续走,给c4赋值:
完成了,最终,成了这样: 分别存储了
name、age、c一、c二、c三、c4
,可是,却不是按咱们赋值的顺序存放,这是由于,苹果为咱们作了字节重排优化!