获取内存大小
上一篇我们简单的提了下内存字节对齐以及为什么要内存字节对齐,那么我们首先看下有什么方式可以获取内存大小。
-
sizeof(type)
主要是获取数据类型
占用的内存大小。
我们先打印下各数据类型占用的内存情况:
//基本数据类型
NSLog(@"char内存大小为%lu",sizeof(char));//char:1
NSLog(@"BOOL内存大小为%lu",sizeof(BOOL)); // BOOL:1
NSLog(@"short内存大小为%lu",sizeof(short)); // short:2
NSLog(@"int内存大小%lu",sizeof(int)); // int:4
NSLog(@"float内存大小为%lu",sizeof(float)); // float:4
NSLog(@"long内存大小为%lu",sizeof(long)); // long:8
NSLog(@"double内存大小为%lu",sizeof(double)); // double:8
//结构体类型
struct Struct1{
int age;
}test1; //4
struct Struct2{
int age;
float point;
}test2; //8
struct Struct3{
double d;
int age;
float point;
}test3; //16
NSLog(@"test1内存大小为%lu,test2内存大小为%lu,test3内存大小为%lu,",sizeof(test1),sizeof(test2),sizeof(test3));
-
class_getInstanceSize
方法可以获取对象在堆空间中实际占用的内存情况,只能传入对象。 -
malloc_size
可以获取实际为对象开辟的内存空间大小情况,只能传入对象。实际分配的和实际占用的内存大小并不相同。
内存对齐的理解
我们已经知道cpu
读取内存是以块
为单位进行读取,如果读取的内容正好在一个块内,那么一次就能读取。如果读取的内容分布在了两个块,那么就需要至少读两次,还要做额为的剔除合并的处理,降低了读取效率。所以读取的内容的末端可以认为这块内容的边界,那么如何用最少的次数读取到内容的处理就是内存对齐。
内存对齐规则
数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。
结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有int ,double等元素,那b应该从8的整数倍开始存储)。
补充:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
总之,内存对齐原则就是:min(m,n) //m为开始的位置,n为所占位数。当m是n的整数倍时,条件满足;否则m位空余,继续取下一位m+1,继续min算法:
结构体对齐
下面我们通过结构体对齐的例子来理解内存对齐原则。
定义两个结构体:
struct OCStruct1{
double a; //8 0-7
char b; //1 8
int c; //4 12-15
short d; //2 16 17
}struct1; 结构体内存大小为24
struct OCStruct2{
double a; //8 0-7
int c; //4 8-11
char b; //1 12
short d; //2 14-15
}struct2; 结构体内存大小为16
NSLog(@"struct1=%lu, struct2=%lu",sizeof(test1),sizeof(test2));
打印后可以看出struct1内存大小为24,struct2内存大小为16。拥有相同的元素,内存大小却不一样,为什么呢?我们根据内存对齐原则来进行计算struct1的内存大小:
- 先存放
double a
,占8个字节,根据min(0,8),从0开始存放8个字节,即0-7位存放a
。 - 然后存放
char b
,占1个字节,起始位置为8,根据min(8,1),从9开始存放1个字节,即8存放b
。 - 然后存放
int c
,占4个字节,起始位置为9,根据min(9,4),4不能被9整除,依次往后找,找到12,从12开始存放4个字节,即12-15存放c
。 - 最后存放
short c
,占2个字节,起始位置为16,根据min(16,2),从16开始存放2个字节,即16-17存放d
。 - 最后根据补充原则,占用内存总大小必须是最大变量占用的内存大小的整数倍,在此例子里即必须是8的倍数,所以内存大小最终确定为24。
同理计算struct2的内存大小:
- 先存放
double a
,占8个字节,根据min(0,8),从0开始存放8个字节,即0-7位存放a
。 - 然后存放
int c
,占4个字节,起始位置为8,根据min(8,4),从8开始存放4个字节,即8-11存放'c'。 - 然后存放
char b
,占1个字节,起始位置为12,根据min(12,1),从12开始存放1个字节,即12存放'b'。 - 最后存放
short d
,占2个字节d,起始位置为13,根据min(16,2),2不能被13整除,继续往后查找,从14开始存放2个字节,即14-15存放'd'。 - 最后根据补充原则,占用内存总大小必须是最大变量占用的内存大小的整数倍,在此例子里即必须是8的倍数,所以内存大小最终确定为16。
内存优化
从上面的例子我们可以看出结构体的内存大小和结构体内成员的顺序是有关系的,根据这点我们在写结构体的时候可以进行属性重排,内存优化。
结构体成员按照内存从大到小的顺序
排列的话,只需要增加少量的内存空余就可以完成内存对齐。所以内存对齐并不是一味的追求用空间换时间,能优化的空间还是最好优化的。
结构体嵌套的内存大小
下面我们根据内存对齐原则看下结构体嵌套结构体的情况:
struct OCStruct3{
char b;
int c;
double a;
short d;
}struct3;
struct OCStruct4{
double a;
int c;
char b;
short d;
struct OCStruct3 stuct3;
}struct4;
NSLog(@"struct3内存大小为%lu,struct4内存大小为%lu",sizeof(struct3),sizeof(struct4));
打印后的struct4内存大小为40,现在我们计算下是不是40:
结构体的嵌套等同于将从子结构体中将成员变量直接放到父成员变量中:
struct OCStruct4{
double a;
int c;
char b;
short d;
struct OCStruct3{
char b;
int c;
double a;
short d;
}struct3;
}struct4;
- 先存放
double a
,占8个字节,根据min(0,8),从0开始存放8个字节,即0-7位存放a
。 - 然后存放
int c
,占4个字节,起始位置为8,根据min(8,4),从8开始存放4个字节,即8-11存放'c'。 - 然后存放
char b
,占1个字节,起始位置为12,根据min(12,1),从12开始存放1个字节,即12存放'b'。 - 然后存放
short d
,占2个字节d,起始位置为13,根据min(16,2),2不能被13整除,继续往后查找,从14开始存放2个字节,即14-15存放'd'。 - 然后存放结构体
struct3
里的char b
,占1个字节,根据内存对齐原则第二条必须从结构体内最大元素内存大小的整数倍,所以应该是8的倍数,而16正好被8整除,所以从16开始存放1个字节,即16存放struct3里的b
。 - 然后存放结构体
struct3
里的int c
,占4个字节,起始位置为17,根据min(17,4),17不能被4整除,往后查找,从20开始存放4个字节,即20-23存放struct3里的c
。 - 然后存放结构体
struct3
里的double a
,占8个字节,起始位置为24,根据min(24,8),从24开始存放8个字节,即24-31存放struct3里的a
。 - 然后存放结构体
struct3
里的short d
,占2个字节,起始位置为32,根据min(32,2),从32开始存放2个字节,即32-33存放struct3里的d
。 - 最后根据补充原则,占用内存总大小必须是最大变量占用的内存大小的整数倍,在此例子里即必须是8的倍数,所以内存大小最终确定为40。
总之,内存对齐的作用不仅是便于cpu快速访问,同时合理的利用内存对齐可以有效地节省存储空间。