C语言中结构体以及Objective-C中对象内存大小计算与对齐方式

355 阅读4分钟

我们知道OC对象的底层实现就是object_class的结构体,因此创建OC对象所需开辟的内存大小与结构体内存开辟是相关的,首先我们需要了解结构体开辟内存的计算方方式:

  • 结构体内存的对齐方式:
  1. 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。
  2. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
  • 对象内存的对齐方式:实际对象所需要的内存大小+16字节对齐(即最终开辟的内存大小为16字节的整数倍)

下面我们可以通过代码来理解:

//JSPerson.h
struct test1 {
    int a;//4字节 0-1-2-3
    double b;//占8字节,从8的整倍数开始 8-9-10-11-12-13-14-15 
    //总共15字节,内部最大成员为b,8字节,所以最终结果为最大成员的整数倍= 16
};

struct test2 {
    char c;//0 占1个字节从0开始
    int d;// 占4字节,从4开始,4-5-6-7
    //总共8字节,4字节对齐后= 8字节
};

struct test3 {
    double e;//占8字节,从0开始 0-1-2-3-4-5-6-7
    char f;//占1字节 从8开始 8
    int g;//占4字节 从12开始 12-13-14-15
    long h;//占8字节 从16开始 16-17-18-19-20-21-22-23
    //总共24字节,刚好为最大成员8字节的整倍数,因此=24字节
};

struct test4 {
    double w;//占8字节,从0开始,0-1-2-3-4-5-6-7
    char h;//站内1字节,8
    struct test1 m;//test1内部成员有int a;double b;首先从最大成员的整数倍开始存储,即从h始,int占4个字节:从8的整数倍开始,16-17-18-19,double占8字节,从24开始,24-25-26-27-28-29-30-31,
    //总工32字节,按8字节对齐后为32字节
};

@interface JSPerson : NSObject
//为什么要用copy,多线程安全考虑
//isa:占8字节,从0开始 0-1-2-3-4-5-6-7
@property (nonatomic, copy) NSString* name;//指针类型占8字节 从8开始,8-9-10-11-12-13-14-15
@property (nonatomic, assign) int age;//占4字节,从16开始,16-17-18-19
@property (nonatomic, copy) NSString* nickName;//占8字节,从24开始,24-25-26-27-28-29-30-31
@property (nonatomic, assign) double height;//占8字节,从32开始,32-33-34-35-36-37-38-39
//总共40字节,按最大属性对齐即8的整数配对齐=40,最后按16位对齐后=48字节
@end

//ViewController.m
@interface ViewController ()

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    struct test1 a;
    struct test2 b;
    struct test3 c;
    struct test4 d;
    JSPerson *p = [[JSPerson alloc] init];
    NSLog(@"test1==%lu",sizeof(a));
    NSLog(@"test2==%lu",sizeof(b));
    NSLog(@"test3==%lu",sizeof(c));
    NSLog(@"test4==%lu",sizeof(d));
    NSLog(@"需要内存大小:%lu,实际开辟内存大小:%zu",class_getInstanceSize([JSPerson class]),malloc_size((__bridge const void *)(p)));
    
}

@end

结果打印:

2021-06-05 12:29:36.105729+0800 memory_calloc[4619:101362] test1==16
2021-06-05 12:29:36.105857+0800 memory_calloc[4619:101362] test2==8
2021-06-05 12:29:36.105968+0800 memory_calloc[4619:101362] test3==24
2021-06-05 12:29:36.106076+0800 memory_calloc[4619:101362] test4==32
2021-06-05 12:29:36.106212+0800 memory_calloc[4619:101362] 需要内存大小:40,实际开辟内存大小:48

注意:JSPerson对象的内存首先按结构体去计算,最后在结果基础上进行16位对齐,即为真实开辟的内存,JSPerson对象影藏了一个ISA属性,从根类中继承来的,是一个指针,占8字节

思考:我们已经知道OC对象开辟内存大小的计算方式,取决于内部的属性,所以属性的排列顺序对开辟内存的大小是有影响的,不同的排列方式完全有可能会造成对象所需开辟内存大小的不同 理论上对象的内存大小最少为16字节,即allo创建一个对象需要开辟内存的大小最为16字节(iOS的16字节对齐),当然把对象置为nil后,其所开辟的内存会被回收掉,此时打印空对象的内存大小结果为0。