iOS开发之结构体底层探索
Posted 卡卡西Sensei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发之结构体底层探索相关的知识,希望对你有一定的参考价值。
说在前面
我们平时写的代码Objective-C,底层实现其实都是C/C++的代码实现的,高级语言经过编译器编译,最终转化为机器语言。
所以,我们的Objective-C的面向对象,其实都是基于C/C++的数据结构实现的。那么Objective-C的对象、类主要是基于C/C++的什么数据结构实现的呢?
1.对象的本质
那到底是什么样的数据结构结构?是数组吗?我们都知道数组只能存储同一种类型的数据,而对象会有不同的属性,比如Student
这个类,它有姓名(string
),身高(double
),等等都是不同的数据类型,很显然不是数组的结构类型!那么很显然只有一种结构能满足,那就是结构体(struct
)。那到底是不是呢?我们来探索一下。
我们建立一个工程,然后编译成C++
看看
编译前
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc]init];
}
return 0;
}
输入一下面这个命令进行编译
clang -rewrite-objc main.m -o main.cpp
我们看到输出了一个C++
的文件main.cpp
打开编译后的文件可以看到,main
函数变成了底下这个屌样子
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是输出的意思
运行如下命令
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个字节。
2.结构体
下面的这个代码是实例化出了一对象obj
,其实底层实现就是上面👆讲的一个结构体,里面会有一个 Class isa
成员变量
NSObject *obj = [[NSObject alloc]init];
假如isa
地址是0x12300001
,obj
这个指针的地址是多少呢???
alloc
已经分配了内存,那么这个指针就是首地址,而里面只有一个成员变量,那么isa
的地址就是结构体在内存中的地址,那么obj=0x12300001
。
结构体的大小是指针的大小,那NSObject
对象在内存中的大小是多少呢?是不是也是8
个字节呢???其实不是的,是16
字节。啊???为什么是16
字节呢???一脸疑惑脸🤔,那我们接着往下探索👇
3.内存对齐
我们可以打印看看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字节对齐,我们继续往下探索👇
4.内存对⻬的原则
- 数据成员对⻬规则:结构(
struct
)(或联合(union
))的数据成员,第
⼀个数据成员放在offset
为0的地⽅,以后每个数据成员存储的起始位置要
从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,
结构体等)的整数倍开始(⽐如int
为4
字节,则要从4的整数倍地址开始存
储。min
(当前开始的位置m
大小n
) 比如:m = 9 n = 4
-->9 10 11 12
- 结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从
其内部最⼤元素⼤⼩的整数倍地址开始存储(struct a
⾥存有struct b
,b
⾥有char
,int
,double
等元素,那b应该从8的整数倍开始存储.) - 收尾⼯作:结构体的总⼤⼩,也就是
sizeof
的结果,必须是其内部最⼤
成员的整数倍不⾜的要补⻬。
5.结构体内存对齐
桂花上代码,先上几个结构体尝尝味儿,哈哈😁
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
,中间9、10、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
。
为了便于理解,画了下面👇这张图
6.总结
- 对象的本质是结构体,可以使用命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件 -o 输出文件
查看底层结构,对象的内存对齐是16字节对齐
- 获取类的成员变量的大小用:
class_getInstanceSize
查看 - 获取一个对象实际开辟的内存大小用:
malloc_size
查看 - 结构(
struct
)(或联合(union
))的数据成员,第⼀个数据成员放在offset
为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int
为4
字节,则要从4的整数倍地址开始存
储。 - 如果⼀个结构⾥有某些结构体成员 ,则
结构体成员
要从其内部最⼤元素
⼤⼩的整数倍地址开始存储,也就是按8字节对齐
,因为指针的大小就8
- 结构体的
总⼤⼩
,必须是其内部最⼤成员的整数倍
,不⾜的要补⻬
。
🌹请收藏+关注,评论 + 转发,以免你下次找不到我,哈哈😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹
以上是关于iOS开发之结构体底层探索的主要内容,如果未能解决你的问题,请参考以下文章