OC和C_C++OC和C_C++
- 一个NSObject对象占多少内存
NSObject *person = [[NSObject alloc] init];
也就是说person指针指向的这段内存空间,占有多少内存空间?
要想知道他在内存中占有多少空间,就要知道他在内存是怎么布局的,在内存中包含哪些内容,搞清楚这段代码的本质是什么,
我们平时编写的OC代码,底层实现其实都是C\C++代码,
编译器会将C\C++转成汇编,然后再转成机器语言运行
所以Objective-C的面向对象都是基于C\C++的数据结构实现的
思考:Objective-C的对象、类主要是基于C\C++的什么数据结构实现的?
结构体
将Objective-C代码转换为C\C++代码
可以安装一个gotocell可以快速定位到终端
代码之后之间的转换肯定是编译器编译的结果,所以要用编译器相关的工具,这里使用使用的clang,clang是xcode内置的编译器llvm的编译器前端
clang -rewrite-objc main.m -o main.cpp
因为我们生成文件是c和c++都有的所有最好生成CPP文件(c plus plus),
不建议直接用上面的来直接转,因为编译也要看我们要转成什么平台的代码,不同平台支持的代码肯定是不一样的,因为我们的代码会转成汇编,会变得运行要根据硬件不同来运行,所以我们只希望生成IOS平台来生成,
模拟器(i386) 32bit(armv7),64bit(arm64)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
没有指定架构的生成文件的大小3.5M,指定之后只有1M多,
NSObject对象的内存本质
我们上面生成文件,就是想看看 [[NSObject alloc] init];他的本质是什么,
我们再相关的文件中可以找到NSObject_IMPL
struct NSObject_IMPL {
Class isa;
};
如果将我们cpp拖进xcode项目,编译会报错,因为cpp是临时生成的,还有一个就是cpp文件也有一个main函数,一个程序只能有一个main函数,所有也会出错,
可以再Xcode的编译文件中,将cpp去掉,
NSObject_IMPL他的意思就是NSObject Implementation,也就是一个NSObject的底层实现,
如果我们直接通过Xcode点进去看一下NSObject的实现可以发现
@interface NSObject {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
@end
简化成
@interface NSObject {
Class isa ;
}
@end
OC的类他的底层实现就是结构体,C\C++的结构体支撑的OC的面向对象
-
问题一个OC对象内存中是如何布局的?
NSObject的底层实现,头文件里面
底层实现
isa 我们可以按进去看一下,他的类型是这个样子,是一个指向结构体的指针
typedef struct objc_class *Class;
既然isa是一个指针那么他在64bit的机器占据8个字节,32bit4个字节,
所以alloc相当于给右边的结构体分配存储空间,分配完成之后,会又一个指针,指向我们的分配的这段空间,假设我们分配的空间isa的地址0x100400110
class_getInstanceSize、malloc_size.
一个NSObject对象占多少内存?
根据我们上面代码分析,我们可能会觉得一个NSObject占用8个字节的内存,实际上不是,实际上是16个字节,但是他利用起来的只有8个字节的大小,我们可以用runtime来验证一下,
class_getInstanceSize
用来获取一个类的实力对象的大小,实例就是我们通过alloc出来的具体对象,
malloc_size
获得指针所指向的内存大小
//获得NSObject的实例对象的成员变量所占用的大小
NSLog(@"%zu大小", class_getInstanceSize([NSObject class]));//8字节
//获得指针所指向的内存大小
NSLog(@"%zd", malloc_size(CFBridgingRetain(person)));// 16字节
苹果开源网站 opensource.apple.com->objc(https://opensource.apple.com/tarballs/objc4/)找到最新的进行下载
解压之后我们直接打开项目,搜索class_getInstanceSize查看源码发现,他会调用一个
返回就是实例对象的成员变量的大小
实际内存中
面试题回答
我们还是回到IOS的源码
通过alloc查看他是否真的是占用了16个字节,实际上调用了allocWithZone
在它里面调用了calloc函数,这里需要传一个size参数,
所以一旦发现小于16他就会分配16个字节,因为他规定所有的对象最低是16个字节,这是corefoundation硬性规定的
- 一个NSObject对象占多少内存?
系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
窥探NSObject的内存
我们可以从另一个角度去验证他是否占了16个字节的内存,Xcode工具,打断点,查看对象的地址
然后通过debug->debugworkflow->view memory
因为内存使用的是16进制,所以两位占一个字节,
通过内存图我们可以看出来我们的对象在内存的分布形式,有颜色的部分就是我们的NSObject的内存分布,可以看出来,他只是用了前8个字节
我们也可以通过打断点之后使用
(lldb)
调试器来进行调试
lldb常用的指令
print、p
:打印
po
:打印对象
内存读取
memory read
/数量
格式字节数
内存地址
x/数量格式字节数 内存地址
x/3xw 0x10010
-
格式
x是16进制,f是浮点,d是10进制字节大小 b:byte 1字节,h:half word 2字节 w:word 4字节,g:giant word 8字节
修改内存中的值
memory write
内存地址 数值
memory write 0x0000010 10
使用例子
Student的本质
@interface Student : NSObject{
@public
int _no; //4字节
int _age;//4字节
}
@end
@implementation Student
@end
通过终端生成C++代码
struct NSObject_IMPL
{
Class isa; //8字节
};
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _no;
int _age;
};
我们写的创建一个对象的方法
Student *student = [[Student alloc] init];
NSLog(@"%zd", class_getInstanceSize([student class]));//16
NSLog(@"%zd", malloc_size((__bridge const void *)(student)));//16
student->_no = 12;
student->_age = 15;
在内存中的分布图其实是这个样子的
因为结构体的地址就是第一个成员变量的地址,所以Student的地址就是isa的地址,内存分布是连续的,接下来的4个字节存储的_no的值,再4个是age的值,
我们可以强制将student指针,转成结构体类型
struct Student_IMPL *stu = (__bridge struct Student_IMPL *)student;
NSLog(@"no=%d,age=%d", stu->_no,stu->_age);//no=12,age=15
也可以正常的访问,进一步说明了,他本质上就是这个结构体类型
CPU从内存读取数据的方式分为大端和小端模式,IOS就是小端模式,读数据会从小的地址开始,所以他的四个字节是 04 00 00 00,
更复杂的集成模式
//Person
@interface Person:NSObject
{
@public
int _no;
}
@end
@implementation Person
@end
//Student
@interface Student:Person
{
@public
int _age;
}
@end
-
思考:一个Person对象、一个Student对象占用多少内存空间?
答案 都是16个字节
内存对其,结构的内存大小,必须是最大成员变量的倍数,
因为Person占居了16个字节,但是最后面的4个字节是空的所以当Student继承了之后,会把自己age放到最后面的4个空字节上面,因此,student还是占据了16个字节,
如果我们增加一个int类型的成员变量,就会占32,
@interface Student:Person //16
{
@public
int _age;//4
int _weight;//4
}
@end
我们通过alloc init出来的实例对象,是不会存储方法的,他只存有成员变量的值,因为方法在内存中指存在一份就够了,不需要每个实例都存一份
回答疑问
计算内存地址,就是一个个的往后数,
12-内存分布注意点
@interface Dog : NSObject
{
//struct NSObject_IMPL NSObject_IVARS;//8个字节
int _no;//4
int _age;//4
int _height;//4
} //理论上他在内存的大小是24字节
NSLog(@"%zd", class_getInstanceSize([dog class]));//24
NSLog(@"%zd", malloc_size((__bridge const void *)(dog)));//32
因为malloc_size结果是32 ,这个是他在内存中被分配的大小,因为他内存对其的单位是16,所以malloc的空间必须是16的倍数.
13-alloc的size分析
在calloc函数中分配内存的时候,传入的size确实就是实力对象的内存大小,24,但是底层内部的实现,会将这个歌内存惊醒对其计算,返回一个对其之后的size在进行实际分配,对其之后就是32