我们现在main.m文件中定义Student对象,如下:
#import <objc/runtime.h>
#import <malloc/malloc.h>
struct Student_IMPL {
Class isa;
int _no;
int _age;
};
@interface Student : NSObject{
@public
int _age;
int _no;
}
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc]init];
stu->_no = 4;
stu->_age = 8;
NSLog(@"%zd",class_getInstanceSize([Student class]));//16
NSLog(@"%zd",malloc_size((__bridge void *)stu));//16
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
NSLog(@"%d, %d",stuImpl->_no,stuImpl->_age);//4, 8
}
return 0;
}
(一)OC对象的内存分配
和上一章一样,转换为C++代码,过程不再赘述,我们找到Student类的定义:
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _no;
};
// @property(nonatomic,assign) NSInteger age;
// @property(nonatomic,assign) NSInteger no;
/* @end */
我们可以看出,Student结构体包含了NSObject结构体,也就是Student的前8个字节是NSObject的成员变量,后面4+4个字节可能是age、no两个成员变量的内存空间(int类型占4个字节)`
我们使用Student_IMPL结构体指针访问stu的成员,成功!
struct Student_IMPL {
Class isa;
int _no;
int _age;
};
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
NSLog(@"%d, %d",stuImpl->_no,stuImpl->_age);//4 8
通过lldb也证实上面的结论:
lldb证实 各占用16个字节,具体论证方法不再赘述(1.c++代码 2. lldb 3. 两个函数)
分析:
Person对象至少需要12(8+4)个字节,最后分配16个字节,空了4个字节,即使没有至少16字节的要求,根据内存对齐,Person对象也是16个字节
Student继承自Person,至少需要16个字节,没有空闲字节
(1)内存分配注意点
我们再看下面的Student类,一个student对象占用多少个字节?:
@interface Student : NSObject{
@public
int _age;
NSInteger _number;
int _no;
NSString *_str;
}
答:至少占用40个字节 isa(8)+age(8 浪费4个)+number(8)+no(8 浪费4字节)+str(8) = 40 因为16个字节为一捆,所以实际分配48个字节;
为什么会产生这样的效果呢?
问题1:为什么会分配48个字节?
我们还是从对象的初始化开始看,类调用alloc
方法,底层实际上调用了allocWithZone
方法,方法调用顺序总结:
allocWithZone
_objc_rootAllocWithZone
-
_class_createInstanceFromZone
方法
在_class_createInstanceFromZone
方法中,我们找到下面的代码:
size = cls->instanceSize(extraBytes);//获取对象至少需要(内存对齐后的)多少字节数 40
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);//重点 size为40 为什么最后分配了48个字节呢?
}
我们查看calloc底层源码到底做了什么事?
调用顺序:
calloc
-
malloc_zone_calloc
在系统底层,分配的字节数永远是16个字节的倍数,因此会分配48个字节
问题3:为什么会实际使用40个字节?
·
猜想: 如果number变量与no变量交换位置,会产生什么效果?
答:age与no连续则至少占用32个字节,实际分配32个字节
如果因为int只占用4个字节,如果两个int变量连续,则刚好不会有内存浪费;不同的成员占用长短字节数不一则会产生内存浪费
所以在定义类时,如果存在占用字节数不同的成员变量,尽可能按字节占用顺序,节约内存空间
网友评论