一. 获取内存的三种方式
1.1 sizeof
sizeof是一个操作符,不是函数sizeof计算的是传进来的数据类型的大小,这个在编译时期就已经确定。sizeof最终得到的结果是该数据类型占用空间的大小
1.2 class_getInstanceSize
class_getInstanceSize是runtime提供的api,用于获取类实例对象所占内存大小,本质是计算`实例的成员变量的内存大小
1.3 malloc_size
malloc_size获取系统实际分配的内存大小
1.4 代码示例
总结:
-
sizeof:打印出来是8字节,因为在对象的本质是结构体指针,而指针的大小就是 8字节 -
class_getInstanceSize:对象实际的内存大小,实际内存大小是由类的成员变量的大小决定的。这里40 = isa(8字节) + name(8字节) + nickname(8字节) + age(4字节) + height(8字节),明明是36为啥输出40呢,这是因为对象内部是8字节对齐方式 -
malloc_size是系统分配的内存大小,是按16字节对齐的方式,即分配的大小是16的倍数 ,不足16的倍数系统会自动填充字节,注意系统的16字节对齐是在实际的内存大小(经过8字节对齐)的基础上。所以这里是48
疑问?
class_getInstanceSize 和 malloc_size 底层做了什么?
class_getInstanceSize 怎么就是是8字节对齐的呢? 而malloc_size又怎么就是是16字节对齐的呢? 莫着急,咱继续。。。
二. 内存对齐原则
-
数据成员的对齐规则可以理解为
min(m, n)的公式, 其中m表示当前成员的开始位置,n表示当前成员所需要的位数。如果满足条件m整除n(即m % n == 0),n从m位置开始存储, 反之继续检查m+1能否整除n, 直到可以整除, 从而就确定了当前成员的开始位置。 -
当结构体嵌套了结构体时,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。如
struct a中嵌套了struct b,b中有char、int、double类型元素,那么b应该从8(double-8字节)的整数倍地址开始存储 -
结构体的内存大小必须是成员中最大对齐数的整数倍,不足的需要补齐。
三. 验证对齐原则
3.1 下表是各种数据类型在ios中的占用内存大小,根据对应类型来计算结构体中内存大小
3.2 创建两个不同的 struct 分别验证
两个结构体的 成员变量是一致的,唯一不同的是成员变量的摆放顺序不同,位置不同导致所占内存大小不同,这就是内存对齐,我们按照内存对齐规则进行分析: :
Struct1分析:
a占1字节 从位置0开始0%1 == 00的位置存ab占8字节 从位置1开始1%8不等于0移到88%8==08-15存bc占4字节 从位置16开始16%4等于016-19存cd占2字节 从位置20开始20%2等于020-21存d- 规则三,内存大小必须为结构体中最大成员
8的整数倍,22变成8的倍数 变成24
Struct2分析:
b占8字节 从位置0开始0%1 == 07的位置存bc占4字节 从位置8开始8%4等于08-11存cd占2字节 从位置12开始12%4等于012-13存da占1字节 从位置14开始14%1等于014存a- 规则三,内存大小必须为结构体中最大成员
8的整数倍,14变成8的整数倍 变成16
3.3 新增一个结构体 Struct3 嵌套 Struct2
Struct3分析:
b占8字节 从位置0开始0%1 == 07的位置存bc占4字节 从位置8开始8%4等于08-11存cd占2字节 从位置12开始12%4等于012-13存da占1字节 从位置14开始14%1等于014存ast2是一个结构体,规则二 结构体成员要从其内部最大成员大小的整数倍开始存储,而st2中最大的成员大小为8所以st2要从8的整数倍开始,当前是从15开始 所以不符合要求,需要往后移动到16,16是8的整数倍,符合内存对齐原则 所以16-31存st2- 规则三,内存大小必须为结构体中最大成员
8的整数倍,32刚刚满足
四. 内存优化
接着上面的结论,我们思考下,为什么要内存对齐,其实苹果这么做的目的让cpu读取内存的效率更高,用空间换取时间
我们创建一个类YJPerson创建一些属性,并赋值查看内存摆放是什么样的?如下:
按照内存对齐原则,第一个为isa,第二个为name,第三个为age 后面按顺序摆放,通过
po 0x0000000b00000012打印发现0x0000000b00000012打印出来不是张三而0x0000000100004010打印出来为张三po 0x0000000100004030为zhangsan那age 和idnum去哪了 看到0x0000000b00000012分开可以看出0x0000000b等于idnum-110x00000012等于age-18可以看出系统进行了优化,属性进行了重排
总结:
大部分的内存都是通过固定的内存块进行读取, 尽管我们在内存中采用了内存对齐的方式,但并不是所有的内存都可以进行浪费的,苹果会自动对属性进行重排,以此来优化内存
五. 内存对齐算法
到目前为止,我们在前文既提到了8字节对齐,也提及了16字节对齐,那我们到底采用哪种字节对齐呢?
我们可以通过objc4中class_getInstanceSize的源码来进行分析
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
#define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
// x+7 & (~7) --> 8字节对齐
// ~:按位取反,7的二进制= 0111 取反后:1000
return (x + WORD_MASK) & ~WORD_MASK;
}
通过源码可知:
- 对于一个
对象来说,其真正的对齐方式是8字节对齐,8字节对齐已经足够满足对象的需求了 - apple系统为了防止一切的容错,采用的是16字节对齐的内存,主要是因为采用8字节对齐时,两个对象的内存会紧挨着,显得比较紧凑,而16字节比较宽松,利于苹果以后的扩展。
总结 综合前文提及的获取内存大小的方式
class_getInstanceSize:是采用8字节对齐,参照的对象的属性内存大小
align16: 16字节对齐算法
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}