咱们平时写的代码Objective-C,底层实现其实都是C/C++的代码实现的,高级语言通过编译器编译,最终转化为机器语言。 因此,咱们的Objective-C的面向对象,其实都是基于C/C++的数据结构实现的。那么Objective-C的对象、类主要是基于C/C++的什么数据结构实现的呢?算法
那究竟是什么样的数据结构结构?是数组吗?咱们都知道数组只能存储同一种类型的数据,而对象会有不一样的属性,好比Student
这个类,它有姓名(string
),身高(double
),等等都是不一样的数据类型,很显然不是数组的结构类型!那么很显然只有一种结构能知足,那就是结构体(struct
)。那究竟是不是呢?咱们来探索一下。数组
咱们创建一个工程,而后编译成C++
看看markdown
编译前数据结构
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc]init];
}
return 0;
}
复制代码
输入一下面这个命令进行编译架构
clang -rewrite-objc main.m -o main.cppapp
咱们看到输出了一个C++
的文件main.cpp
打开编译后的文件能够看到,
main
函数变成了底下这个屌样子iphone
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
return 0;
}
复制代码
不一样平台下面的代码是不同,好比Windows,macOS,iOS
,那么咱们确定是但愿是支持iOS
系统下的C++
代码,那么用下面这个命令函数
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件 -o 输出文件学习
意思就是Xcode编译是跑在arm64架构的iPhone平台上的,-o是输出的意思ui
运行以下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
编译完的底层源码有好几万行,在7400行
,咱们能够看到,NSObject
的底层是结构体
struct NSObject_IMPL {
Class isa;
};
复制代码
这也就验证了OC
的底层是结构体,那么这个Class isa
是个什么东东呢? 咱们进入里面看看
typedef struct objc_class *Class;
复制代码
这个 *Class
就是指向结构体的指针,那么指针在结构里面占多少字节呢?若是是64位就是8个字节,32位就是4个字节,那么如今的系统都是64位的了,在这个结构体里面,Class isa
成员变量就是占8个字节。那么这个结构体也就是8个字节,由于如今这个结构体里面没有其余的属性和变量,这个Class isa
成员变量是默认带上的,因此结构体就是占8个字节。
下面的这个代码是实例化出了一对象obj
,其实底层实现就是上面👆讲的一个结构体,里面会有一个 Class isa
成员变量
NSObject *obj = [[NSObject alloc]init];
复制代码
假如isa
地址是0x12300001
,obj
这个指针的地址是多少呢??? alloc
已经分配了内存,那么这个指针就是首地址,而里面只有一个成员变量,那么isa
的地址就是结构体在内存中的地址,那么obj=0x12300001
。 结构体的大小是指针的大小,那NSObject
对象在内存中的大小是多少呢?是否是也是8
个字节呢???其实不是的,是16
字节。啊???为何是16
字节呢???一脸疑惑脸🤔,那咱们接着往下探索👇
咱们能够打印看看NSObject
实例对象的成员变量所占用的内存大小 引入头文件 #import<objc/runtime.h>
#import<objc/runtime.h>
NSLog(@"InstanceSize:%zd",class_getInstanceSize([NSObject class]));
复制代码
咱们能够看到输出的结果是
8
那咱们再看看,obj
所指向的内存的大小 导入头文件#import<malloc/malloc.h>
NSLog(@"malloc_size:%zd",malloc_size((__bridge const void *)(obj)));
复制代码
打印结果以下 从打印的结果来看,是
16
,这也就验证了上面👆说的:NSObject
对象在内存中的大小是16
字节。po
打印一下 也能够查看地址在内存中的分布
Debug->Debug Workflow->View Memory
下图就是内存分配状况
从上图很明显看出来是
16字节
,前8
位是isa
,后8
位就是内存分配预留的8
字节。那为何要预留呢?明明8
个就够用了,分配16
干吗???内存资源
是很珍贵的啊!CPU
是否是傻啊???带着这个疑问,咱们继续往下探索👇
下面的代码打印结果是多少呢???
@interface Student : NSObject
{
int _age;
int _num;
}
@end
@implementation Student
@end
Student *stu = [[Student alloc]init];
NSLog(@"InstanceSize:%zd",class_getInstanceSize([Student class]));
NSLog(@"malloc_size:%zd",malloc_size((__bridge const void *)(stu)));
NSLog(@"sizeof:%lu",sizeof(stu));
复制代码
打印结果 经过命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
转为cpp文件
是以下结构
struct Student_IMPL {
Class isa;
int _age;
int _num;
};
复制代码
由于Student
是继承自NSObjec
t,Student
的结构体里面有个成员变量isa
是指向父类NSObject
的。isa
是8
个字节,int
类型是4
个字节,一共就是16
字节。那我Student
里面少一个成员变量呢?那结果是12
仍是16
呢???? 结果是
16
,why???为何呢?这就是字节对齐
,class_getInstanceSize
是计算类的成员变量的大小,实际上计算的并非严格意义上的对象的内存的大小,由于内存进行了8
字节对齐,从objc
的底层源码能够看到,核心算法是define WORD_MASK 7UL ((x + WORD_MASK) & ~WORD_MASK
。
补充:
sizeof
不是一个函数,是C/C++
中的一个操做符(operator)sizeof()
是一个判断数据类型或者表达式长度的运算符。
实际上对象的内存对齐是16字节对齐,咱们继续往下探索👇
struct
)(或联合(union
))的数据成员,第⼀个数据成员放在offset
为0的地⽅,之后每一个数据成员存储的起始位置要 从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组, 结构体等)的整数倍开始(⽐如int
为4
字节,则要从4的整数倍地址开始存 储。 min
(当前开始的位置m
大小n
) 好比: m = 9 n = 4
--> 9 10 11 12
2. 结构体做为成员:若是⼀个结构⾥有某些结构体成员,则结构体成员要从 其内部最⼤元素⼤⼩的整数倍地址开始存储(struct a
⾥存有struct b
,b
⾥有char
,int
,double
等元素,那b应该从8的整数倍开始存储.) 3. 收尾⼯做:结构体的总⼤⼩,也就是sizeof
的结果,必须是其内部最⼤ 成员的整数倍不⾜的要补⻬。
桂花上代码,先上几个结构体尝尝味儿,哈哈😁
struct Student1{
double a; // 8 [0 7]
char b; // 1 [8]
int c; // 4 (9 10 11 [12 13 14 15]
short d; // 2 [16 17] 24
} Student1;
struct Student2{
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15] 16
} Student2;
NSLog(@"Student1:%lu-Student2:%lu",sizeof(Student1),sizeof(Student2));
复制代码
下面就开始细细的品尝这两道开胃小菜吧!根据内存对齐原则进行简单的计算和分析
Student1
内存大小详细过程min(m,n)
,m
表示当前开始的位置,n
表示大小)
a
: 占8
个字节,offert从0开始, min(0,8)
, 即0 ~ 7
存放a
b
: 占1
个字节,offert从8开始, min(8,1)
, 即8 ~ 8
存放b
c
: 占4
个字节,offert从12开始,min(12,4)
,即12 ~ 15
存放c
,中间九、十、11不是4的倍数,因此得空出来。d
: 占2
个字节,offert从14开始,min(16,2)
,即16~17
存放d
下面👇放上
Student1
的内存分布图,便于理解
根据
对齐原则3
,结构体的总⼤⼩,必须是其内部最⼤
成员的整数倍
,不⾜的要补⻬
,Student1
中最大的是8
,因此最后为24
。
Student2
内存大小分析
a
: 占8
个字节,offert从0开始, min(0,8)
, 即0 ~ 7
存放a
b
: 占4
个字节,offert从8开始, min(8,4)
, 即8 ~ 11
存放b
c
: 占1
个字节,offert从12开始,min(12,1)
,即12 ~ 12
存放c
d
: 占2
个字节,offert从14开始,min(14,2)
,即14~15
存放d
下面👇放上
Student2
的内存分布图,便于理解
为何
d
的存放不从13
开始,由于13
不是2整数倍
,因此从14
开始,根据对齐原则,最后为16
。
开胃菜吃完了,那就再来道硬菜,7788。 桂花上菜!!!!!
struct Student3 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15]
int e; // 4 [16 17 18 19]
struct Student1 str;//(20 21 22 23 [24 ~ 47]
}Student3;
NSLog(@"Student1:%lu-Student2:%lu-Student3:%lu",sizeof(Student1),sizeof(Student2),sizeof(Student3));
复制代码
打印结果以下
Student3
内存大小分析
a
: 占8
个字节,offert从0开始, min(0,8)
, 即0 ~ 7
存放a
b
: 占4
个字节,offert从8开始, min(8,4)
, 即8 ~ 11
存放b
c
: 占1
个字节,offert从12开始,min(12,1)
,即12 ~ 12
存放c
d
: 占2
个字节,offert从14开始,min(14,2)
,即14~15
存放d
e
: 占4
个字节,offert从16开始,min(16,4)
,即16~19
存放e
这道Student3
菜确实有点硬啊!得好好啃一啃了。Student3
的其余成员就不作过多分析了,上面也有,主要分析下这个Student3
里面的struct Student1 str
,这是一个嵌套的结构体
,结构体里面还嵌套了一个结构体。成员struct Student1 str
其实就是Student1结构体
,上面我已经知道了Student1
的内存大小是24
了,而Student3
里面的其余成员所占用的是19
,Student1
里面成员的最大值是8
,因此offert
必须是8
的倍数,也就是从24
开始,连续开辟24
字节的内存空间来存储struct Student1 str
。
为了便于理解,画了下面👇这张图
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件 -o 输出文件
查看底层结构,对象的内存对齐是16字节对齐
class_getInstanceSize
查看malloc_size
查看struct
)(或联合(union
))的数据成员,第⼀个数据成员放在offset
为0的地⽅,之后每一个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int
为4
字节,则要从4的整数倍地址开始存储。 6. 若是⼀个结构⾥有某些结构体成员 ,则结构体成员
要从其内部最⼤元素
⼤⼩的整数倍地址开始存储,也就是按8字节对齐
,由于指针的大小就8
4. 结构体的总⼤⼩
,必须是其内部最⼤成员的整数倍
,不⾜的要补⻬
。
🌹请收藏+关注,评论 + 转发,以避免你下次找不到我,哈哈😁🌹
🌹欢迎你们留言交流,批评指正,互相学习😁,提高自我🌹