一个OC对象占用多少内存
NSObject * obj = [[NSObject alloc] init];
解答:
- 系统分配了16个字节给
NSObject
对象可通过malloc_size函数获得
- 但是
NSObject
对象只使用了8个字节(64bit环境下,源码中未区分架构)
,可通过class_getInstanceSize函数获得
剖析:
该问题问的是obj
指针所指向的那块内存空间占用多少内存
我们平时编写的
OC代码
,底层实现都是C/C++代码
.
OC
--->C/C++
---->汇编语言
----->机器语言
所以OC
的面向对象
都是基于C/C++的数据结构(结构体
)实现的
将OC
代码通过终端转成C/C++
代码来探究NSObject
本质.
1.终端进入目标文件所在目录
2.输入指令
clang -rewrite-objc main.m -o main.cpp
//通过clang编译器把main.m文件转成C++文件.因为C++支持C语言,所以转成C++更合适.
//该指令生成的.cpp文件并没有指定是哪个平台的代码,
//同样的OC代码在不同平台转成的C++代码也不同.所以我们需要指定生成那个平台下的C++代码:
//模拟器(i386),32bit(armv7),64bit(arm64)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//指令解读:
//xcrun : xcode run
//iphoneos : 指定平台
//arch arm64 : 指定架构
//main-arm64.cpp : 指定生成目标文件为64位架构下的C++文件.
//指定架构后会发现生成的文件明显比未指定架构的文件要小
当把cpp文件放入项目中会发现编译报错,不让该文件参与编译即可解决.
image.png
3.在原目标文件路径下就可以查看main.cpp文件.
查看.cpp
文件技巧
- 文件检索
NSObject
的定义NSObject_IMPL {
因为我们知道OC对象本质是基于C和C++的结构体实现的,所以NSObject
定义一定是个结构体.这样就可以快速的在大量代码的文件中查找的目标代码.
OC
中NSObjcet
的定义在C/C++
中如下
//NSObject_IMPL : NSObject implementation(定义实现)
struct NSObject_IMPL {
Class isa;
};
再次查看OC中NSObject
的定义:
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
简化后
@interface NSObject {
Class isa;
}
总结:NSObject
的底层实现:
查看
Class isa
:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
//Class是一个`struct objc_class *`类型的结构体指针
结论:
- OC中的一个
NSObject
对象底层实现是一个结构体,该结构体有一个成员变量Class isa
,所以一个NSObject
对象占用多少内存就转化成Class isa
这个成员变量占用多少内存. -
Class isa
是一个结构体指针,在32位系统上占用4个字节;在64位系统占用8个字节. - 该
NSObject
结构体的地址值就是该结构体第一个成员变量的地址值,即isa的地址值.
导入<objc/runtime.h> <malloc/malloc.h>检查NSObject
对象占用内存大小
//获取NSObject的instance对象成员变量的大小
//class_getInstanceSize(<#Class _Nullable __unsafe_unretained cls#>)
NSLog(@"%zd",class_getInstanceSize([NSObject class]));//8字节
//获取obj指针所指向内存的大小
//malloc_size(<#const void *ptr#>)
NSLog(@"%zu",malloc_size((__bridge const void *)(obj)));//16字节
//malloc_size接收的参数是一个const void *类型的C语言指针
//所以需要将obj这个OC指针做个桥接转成C语言的指针
我们要注意malloc_size
函数传入的是一个指向NSObject
的instance
对象的指针,所以malloc_size
函数返回的是该指针所指向的对象所分配的内存大小
那class_getInstanceSize
返回的又是什么?我们可以通过Apple开源的代码(https://opensource.apple.com)
来查看该函数的实现,进而了解该函数.我们需要的开源代码地址为https://opensource.apple.com/tarballs
.进入后检索objc4
.进入该目录下会发现有很多开源的代码,我们筛选最新的代码查看,最新的代码一般来说需要根据文件名字 和 该文件所占大小 综合比较选出我们需要的源码.我们此次下载的是objc4-723.tar.gz.下载后检索class_getInstanceSize
.实现在.m
文件.我们会发现该函数的实现如下:
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
我们发现调用的是cls
的alignedInstanceSize()
方法aligned
意为:对准的,均衡的,排整齐
// Class's ivar size rounded up to a pointer-size boundary.返回的是instance对象的成员变量(Class's ivar)的大小
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
结论:
当一个NSObject
对象被创建时系统分配的是16个字节,但是该对象只占用了8个字节,因为NSObject
对象的底层是一个结构体,该结构体内只有一个结构体指针 Class isa
占用8个字节.
通过查看NSObject
对象的alloc
函数底层实现来验证初始化分配16个字节.
NSObject
对象的alloc
函数底层调用的是allocWithZone
方法.在源码中检索allocWithZone
,查看该函数的底层实现:
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
id obj;
#if __OBJC2__
// allocWithZone under __OBJC2__ ignores the zone parameter
(void)zone;
obj = class_createInstance(cls, 0);
#else
if (!zone) {
obj = class_createInstance(cls, 0);
}
else {
obj = class_createInstanceFromZone(cls, 0, zone);
}
#endif
if (slowpath(!obj)) obj = callBadAllocHandler(cls);
return obj;
}
简单来看调用NSObject
的alloc
函数底层会调用上面方法,返回一个id
类型.该函数主要代码在obj = class_createInstance(cls, 0);
查看该函数实现:
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
查看_class_createInstanceFromZone
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
主要看下面的内存分配代码:
查看
instanceSize(extraBytes)
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
size_t size = alignedInstanceSize() + extraBytes;//8字节
.alignedInstanceSize()获取成员变量大小
从上面的代码可以看出,一个NSObject
对象内存如果小于16字节,就分配16个字节.所以一个NSObject
对象至少分配16个字节.
通过Xcode推断该问题答案
image.pngimage.png
image.png
上面16个字节的内存空间即代表
NSObject
的内存占用情况.
字节: Byte
通常1 B(一个字节)
有8位.一个位就代表一个二进制的0或1,所以一个字节有八位二进制位,即2的八次幂,1个字节的范围是 0 ~ 255.
一个16进制位代表四个二进制位,两个16进制位代表八个二进制位,即两个16进制位就是一个字节八位二进制数
.Xcode显示的是16进制.上图中每两位数字
代表一个字节.通过阅读源码我们了解到:一个OC对象一般分配16个字节.
由上图可以看出:
从0x102801c80
地址值开始到之后的16个字节代表的是NSObject
所占用的内存空间,改16个字节中,前八位字节已经被占用,剩余八位都为零.前八位即为isa
指针的地址.
简单的lldb
指令
打印
image.pngp print 指令
po 表示把打印目标视为一个OC对象来打印
读取内存
memory read
简写 x
//memory read/数量+格式+字节数 内存地址
//x/数量+格式+字节数 内存地址
//格式:
//x是16进制,f是浮点数,d是十进制
//字节大小:
//b: byte 1字节;h: half word 2字节
//w :word 4字节; g : giant word 8字节
x/3xw 0x10010
以十进制
方式 读取内存
(memory read : x)
四串
每串
是4个字节
以
16进制
方式 读取内存
(memory read : x)
四串
每串
是4个字节
image.png
通过lldb指令修改内存中的数据
将下图指定内存地址的值修改为7 :
image.png
其实位置地址为0x102801c80,目标位置为下数7个字节,所以内存地址为0x102801c87
memory write 0x102801c87 7
image.png
如图可看到
1D
后面的数据由00
变成了07
网友评论