一、获取内存大小的三种方式
1.1 sizeOf
sizeof
是一个操作符,不是函数sizeof
计算内存大小时,传入的主要是对象或者数据类型,在编译器的编译阶段
大小就已经确定sizeof
计算出的结果,是表示占用空间的大小
1.2 class_getInstanceSize
class_getInstanceSize
runtime 提供的api,用于获取类实例对象所占内存大小
,本质也是计算该实例中所有成员变量的内存大小
1.3 malloc_size
malloc_size
获取系统实际分配的内存大小
1.4 code示例
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end
#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
LGPerson *p = [LGPerson alloc];
NSLog(@"p对象类型占用的内存大小:%lu",sizeof(p));
NSLog(@"p对象实际占用的内存大小:%lu",class_getInstanceSize([p class]));
NSLog(@"p对象实际分配的内存大小:%lu",malloc_size((__bridge const void*)(p)));
return 0;
}
复制代码
打印结果如下:
总结
sizeof
打印的p
为对象指针地址
一个指针的大小为8
字节class_getInstanceSize
打印的是内部成员变量
的大小isa
占8
字节name
字符串8
字节age
int
类型4
字节8+8+4=20
因为类的成员变量按照8
字节对齐规则20->24
malloc_size
为系统实际分配的大小,分配给对象的大小是按照16
字节对齐,16
字节对齐的算法 简单理解为24+(16 -1 )>>4 <<4
得到32
二、内存对齐规则
- 【原则一】 数据成员的对齐规则可以理解为
min(m, n)
的公式, 其中 m表示当前成员的开始位置,n
表示当前成员所需要的位数。如果满足条件m
整除n
(即m % n == 0
),n
从m
位置开始存储, 反之继续检查m+1
能否整除n
, 直到可以整除, 从而就确定了当前成员的开始位置。 - 【原则二】数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体
a
嵌套结构体b
,b
中有char
、int
、double
等,则b
的自身长度为8
- 【原则三】最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的需要补齐。
三、验证规则
1.1 各数据类型所占字节大小
1.2 创建两个不同的结构体 myStuct1,myStruct2 分别进行验证 code如下
struct myStruct1 {
char a; // 1
double b; //8
int c; //4
short d; //2
}myStruct1;
struct myStruct2 {
double b;//8
int c;//4
short d;//2
char a;//1
}myStruct2;
NSLog(@"%lu-%lu",sizeof(myStruct1),sizeof(myStruct2));
复制代码
打印结果如下:24-16
结果分析:两个结构体的 成员变量是一致的,唯一不同的是成员变量的摆放顺序不同,位置不同导致所占内存大小不同,这就是内存对齐,我们按照内存对齐规则进行分析:
分析结构体myStruct1
- 【1】
a
占1
字节 从位置0
开始0%1 == 0
0
的位置存a
- 【2】
b
占8
字节 从位置1
开始1%8
不等于0
移到8
8%8==0
8-15
存b
- 【3】
c
占4
字节 从位置16
开始16%4
等于0
16-19
存c
- 【4】
d
占2
字节 从位置2
0开始20%2
等于0
20-21
存d
- 【5】规则三,内存大小必须为结构体中最大成员
8
的整数倍,22
变成8
的倍数 变成24
分析结构体myStruct2
- 【1】
b
占8
字节 从位置0
开始0%1 == 0
7
的位置存b
- 【2】
c
占4
字节 从位置8
开始8%4
等于0
8-11
存c
- 【3】
d
占2
字节 从位置12
开始12%4
等于0
12-13
存d
- 【4】
a
占1
字节 从位置14
开始14%1
等于0
14
存a
- 【5】规则三,内存大小必须为结构体中最大成员
8
的整数倍,14
变成8
的整数倍 变成16
1.3 新增一个结构体myStruct3 嵌套myStrct2 code如下
struct myStruct2 {
double b;//8
int c;//4
short d;//2
char a;//1
}myStruct2;
struct myStruct3 {
double b;//8
int c;//4
short d;//2
char a;//1
struct myStruct2 str;
}myStruct3;
NSLog(@"%lu",sizeof(myStruct3));
复制代码
打印结果如下:32
分析结构体myStruct3
- 【1】
b
占8
字节 从位置0
开始0%1 == 0
7
的位置存b
- 【2】
c
占4
字节 从位置8
开始8%4
等于0
8-11
存c
- 【3】
d
占2
字节 从位置12
开始12%4
等于0
12-13
存d
- 【4】
a
占1
字节 从位置14
开始14%1
等于0
14
存a
- 【5】
str
是一个结构体,规则二 结构体成员要从其内部最大成员大小的整数倍开始存储,而myStruct2
中最大的成员大小为8
所以str
要从8
的整数倍开始,当前是从15
开始 所以不符合要求,需要往后移动到16
,16
是8
的整数倍,符合内存对齐原则 所以16-31
存str
- 【5】规则三,内存大小必须为结构体中最大成员
8
的整数倍,32
刚刚满足
四、内存优化
思考内存优化
接着上面的结论,我们思考下,为什么要内存对齐,其实苹果这么做的目的让cpu读取内存的效率更高,用空间换取时间
我们创建一个类LGPerson创建一些属性,并赋值查看内存摆放是什么样的?code如下:
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic,assign)int height;
@end
LGPerson *p = [LGPerson alloc];
p.name=@"lb";
p.nickName=@"LB";
p.age=18;
p.height=180;
复制代码
断点查看p对象的内存地址
按照内存对齐原则,第一个为isa,第二个为name,第三个为age,后面按顺序摆放,通过
po 0x000000b400000012
打印发现0x000000b400000012
打印出来不是lb
而0x0000000100004010
打印出来为lb
po 0x0000000100004030
为LB
那age 和height去哪了 看到0x000000b400000012
分开可以看出0x000000b4
等于180
0x00000012
等于18
可以看出系统进行了优化,属性进行了重排
总结
所以,这里可以总结下苹果中的内存对齐思想:
大部分的内存都是通过固定的内存块进行读取,
尽管我们在内存中采用了内存对齐的方式,但并不是所有的内存都可以进行浪费的,苹果会自动对属性进行重排,以此来优化内存
五、为什么分配内存大于实际占用内存
通过上面的探索,我们知道对象的内存是根据你成员变量内存大小通过
8
字节对齐来计算的 但是实际内存分配大小不是按照8字节来的,是按照16
字节对齐来的 我们可以通过objc源码来探究 code如下
通过 size = cls->instanceSize(extraBytes); //计算大小
//alignedInstanceSize() + extraBytes; //内存对齐方法
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
static inline uint32_t word_align(uint32_t x) {
//x+7 & (~7) --> 8字节对齐
return (x + WORD_MASK) & ~WORD_MASK;
}
# define WORD_MASK 7UL
复制代码
通过源码可以知道,计算内存大小实际是按照8字节对齐计算的
class_getInstanceSize
:是采用8字节对齐,参照的对象的属性内存大小malloc_size
:采用16字节对齐,参照的整个对象的内存大小,对象实际分配的内存大小必须是16的整数倍
总结:
- 对象大小和成员变量的类型和数量有关
- 对象实际内存是按照8字节对齐
- 对象分配内存大小按照16字节对齐。
- 16字节对齐方法,内存占用大小x x+15 >>4 <<4 得到内存分配大小
ps:逻辑教育牛逼
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END