(一)问题:一个NSObject对象占用多少内存?
需要储备的知识点:
- 熟悉OC代码的底层实现
我们平时编写的OC代码,底层其实都是C/C++代码
所以OC的面向对象都是基于C/C++的数据结构(结构体)来实现
- 将OC的代码转换成C/C++代码
clang -rewrite-objc main.m -o main.cpp
xcrun -sdk ipnoneos clang -rewrite-objc main.m -o main.cpp
xcrun -sdk ipnoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
clang -fobjc-arc -framework Foundation main.m -o main.cpp
- xcrun:Xcode运行
- iphoneos:苹果手机系统
- arm64:不同平台支持的代码不一样
Windows、mac、iOS
模拟器(i387)、32bit(armv7)、64bit(arm64) - main.m :OC源文件名
- main.cpp :输出的cpp文件
支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
有时候报错:/usr/include/mach/machine/vm_param.h:35:2: error: architecture not supported
error architecture not supported
去除掉-arch arm64就可以,具体原因不明。
3.NSObject的底层实现
//OC中NSObject定义
@interface NSObject <NSObject> {
Class isa ;
}
//转成cpp后的NSObject定义
struct NSObject_IMPL {
Class isa; // 8个字节
};
// Class的定义(为一个指针)
typedef struct objc_class *Class;
注:一个指针占4个字节。指针即为地址,指针占几个字节跟语言无关,而是跟系统的寻址能力有关,譬如:以前是16位地址,指针即为2个字节,现在一般是32位系统,所以是4个字节,以后64位,则就占8个字节。
因此isa指针在内存中占8个字节。
//NSObject对象内存中只有一个isa指针,内存地址假如为 0x100400110
//生成的obj的地址就为0x100400110
NSObject *obj = [[NSObject alloc] init];
// 16个字节
//引入#import <objc/runtime.h>
// 获得NSObject实例对象的成员变量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
//引入#import <objc/runtime.h>
// 获得obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
这里是OC源码地址
https://opensource.apple.com/tarballs/
(1)、通过查看class_getInstanceSize源码,得知class_getInstanceSize函数打印为8的原因;
//class_getInstanceSize源码
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// 获得实力对象的成员变量所占用的大小
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
(2)、通过查看alloc分配内存源码源码,得知malloc_size函数打印为16的原因;
alloc分配内存源码
CF要求所有对象至少16字节
具体源码如下:
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;
}
——————————————————————————————————————
// obj = class_createInstance(cls, 0);
id class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);//——————>点击进入方法
}
——————————————————————————————————————
//_class_createInstanceFromZone(cls, extraBytes, nil);
static __attribute__((always_inline))
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;
}
——————————————————————————————————————
//size_t size = cls->instanceSize(extraBytes);
//系统分配内存,获取当前实力对象的内存大小(只包含isa指针,为8个字节),如果小于16个字节,会直接分配16字节。
size_t instanceSize(size_t extraBytes) {
//获取当前实力对象的内存大小(只包含isa指针,为8个字节)
size_t size = alignedInstanceSize() + extraBytes;//——————>点击进入方法
// CF 要求所有对象至少为16个字节。.
//小于16个字节,会直接分配16字节。
if (size < 16) size = 16;
return size;
}
——————————————————————————————————————
// size_t size = alignedInstanceSize() + extraBytes;
//class_getInstanceSize的方法也是调用的这个函数
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
NSObject内存布局
总结:1. 系统跟配了16个字节给NSObject对象(通过malloc_size函数获得)
2.但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
注:2个容易混淆的函数
//创建一个实例对象,实际上分配了多少内存?
#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);
//创建一个实例对象,至少需要多少内存?
//类似sizeof(),区别为传入参数不同,sizeof为运算符,不为函数,是传入的类型为多大,编译时直接确定,比如传入person对象,显示是person的类型,指针的大小。class_getInstanceSize为函数,传递为类,显示传入类的大小
#import <objc/runtime.h>
class_getInstanceSize([NSObject class]);
例如:
struct MJPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //8
int _age;//4
int _height;//4
int _no;//4
}; // 计算结构体大小,内存对齐,24
@interface Person : NSObject
{
int _age;
int _height;
int _no;
}
@end
@implementation MJPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[MJPerson alloc] init];
NSLog(@"%zd %zd",
malloc_size((__bridge const void *)(p))); // 32
class_getInstanceSize([Person class]), // 24
}
return 0;
}
分析:
1.malloc_size()为类对象动态分配的内存大小,是操作系统内存对齐之后的大小,补齐规则为16的整数倍。即如果一个类对象需要的内存大小为20,通过malloc_size()方法获取的大小为32,即16的倍数;(16字节原因:通过libmalloc源码获知,其中一种分配方式:操作系统提前规划好了为16个字节倍数的空间,最大为256,使用时,系统直接取出对应空间大小的内存)
2.class_getInstanceSize()方法获取的内存大小是结构体内存对齐之后的大小,补齐规则按照类的成员变量所占内存最大值的整数倍。即如果一个类对象需要的内存大小为20,最大成员为isa内存占用8字节,通过class_getInstanceSize()方法获取的大小为24,即8的倍数
(二)思考:一个Person对象、一个Student对象占用多少内存空间?
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8
int _age; // 4
}; // 16 内存对齐:结构体的大小必须是最大成员大小的倍数
struct Student_IMPL {
struct Person_IMPL Person_IVARS; // 16
int _no; // 4 存在于没有使用的那四个字节中
}; // 16
如图:
Person 对象和Student对象的C++代码
Person 对象和Student对象内存图片
答案:都为16字节。
网友评论