iOS 内存对齐

iOS 内存对齐_第1张图片
数据类型所占字节.png

1. 对象内存对齐

在我们进行alloc一个对象的时候,通过源码探究可以得出。一个对象进行内存的开辟,是以16字节进行对齐的。但是,对象真正需要的内存,却是以8字节对齐的。只是系统为了内存安全考虑,采取16字节对齐的方式开辟内存空间。

ZCPerson.h

@interface ZCPerson : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, copy) NSString *nickname;

@property (nonatomic, assign) int age;

@property (nonatomic, assign) double height;

@end

main.m

int main(int argc, char * argv[]) {
    @autoreleasepool {
       
        ZCPerson *p = [[ZCPerson alloc]init];
        p.name      = @"zc";
        p.age       = 18;
        p.height    = 185.0;
           
        NSLog(@"%@---%lu---%lu---%lu",p,sizeof(p),class_getInstanceSize([ZCPerson class]),malloc_size((__bridge const void *)(p)));
        
    }
    return 0;
}

输出:

2020-11-24 10:36:39.442212+0800 alloc[2456:59130] ---8---40---48

通过打印,我们可以看到第一个是输出ZCPerson对象,第二个打印的是p指针,指针在内存中占据8个字节。第三个打印是ZCPerson所需内存,我们看到输出结果是40,如果是按照alloc源码分析,以16字节对齐的话,应该和最后输出的一样是48,所以从输出结果看,ZCPerson所需内存是不可能以16自己对齐的。

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}
 uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
#   define WORD_MASK 7UL

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

通过观察class_getInstanceSize方法源码发现,ZCPerson实际所需要的内存空间是以8字节对齐进行开辟的。

  • 系统内存优化
    ZCPerson.h
@property (nonatomic, copy) NSString *name;

@property (nonatomic, copy) NSString *nickname;

@property (nonatomic, assign) int age;

@property (nonatomic, assign) double height;

@property (nonatomic) char c1;

@property (nonatomic) char c2;

iOS 内存对齐_第2张图片
内存优化.png

通过 x命令可以打出地址内存情况,通过 x/4gx可以将内存情况以16进制打印出4组数据。

输出ZCPerson:

(lldb) po p

打印ZCPerson地址内存情况:

x/4gx 0x600003f11260
0x600003f11260: 0x000000010b644858 0x0000001200006261
0x600003f11270: 0x000000010b642068 0x000000010b642088
(lldb) po 0x000000010b644858
ZCPerson

(lldb) po 0x000000010b642068
zc

(lldb) po 0x000000010b642088
handsomeboy

从以上输出可以看到,这里左边打印出的是地址,右边对应的是地址里存放的是属性的值。但是,当我们打印第一行第二列的值的时候,发现打出来的是乱码:

(lldb) po 0x0000001200006261
77309436513

这是因为,ZCPerson内存被优化的结果。在上述中我们可以看到,给ZCPerson属性赋值的还有age,c1,c2,而这些值,正好被存储在0x0000001200006261这里。
验证如下:

(lldb) po 0x00000012
18

(lldb) po 0x00000000000061
97

(lldb) po 0x00000000000062
98

通过打印我们可以看到打印出了18,而打印出的97,98也正好对应了ASCII码中的a,b,从而,也说明了ZCPerson在开辟内存的时候,进行了内存的重排,这样做是为了避免内存的浪费,起到优化内存的作用。

2. 结构体内存对齐

在iOS中,类在底层会被编译成结构体,那么结构体是否是和类一样也是按照16字节对齐的呢?
我们定义2个结构体如下,然后打印出他们的内存大小:

struct ZCStruct1 {
    double a;   
    char b;    
    int c;    
    short d;   
}struct1;

struct ZCStruct2 {
    double a;  
    int b;      
    char c;     
    short d;   
}struct2;
NSLog(@"%lu -- %lu",sizeof(struct1),sizeof(struct2));

按照猜想,在struct1中,a的字节大小为8,b的字节大小为1,c的字节大小为4,d的字节大小为2,加起来等于15,按照16字节对齐原则,打印出来的的struct1的内存大小应该为16,同理,struct2打印出来的大小也应该为16。通过输出,我们发现打印结果与我们的猜想,完全不一致,哈哈,猜想错误。

打印结果:

2020-11-24 15:16:52.961015+0800 alloc[6174:186368] 24 -- 16

在结构体中进行内存对齐时,要遵循以下原则:

    1. 数据成员对齐规则:结构体或者联合体的数据成员,第一个数据成员放在offset为0的地方,接下来的数据成员存储的位置要从该成员的大小或者成员的子成员大小(比如数组、结构体等)的整数倍开始。
    1. 结构体作为成员:如果一个结构体内有其他结构体成员,则结构体要从其内部最大元素大小的整数倍地址开始存储。(假设struct a 里有struct b,b内部含有char,int,double,那么b要从8开始存)。
    1. 结构体的总大小,必须是其内部最大成员的整数倍,不足的要补齐。

按照以上原则,我们重新来计算struct1的大小:

  • double a:double修饰的字节大小为8,因为在struct1中没有其他struct类型的数据,所以我们从0开始存8个字节,所占内存空间应该是[0~7];
  • char b:char修饰的字节大小为1,按照顺序,从第8位开始存,因为8正好是1的倍数,所以b所占空间为[8],大小是1个字节;
  • int c:int修饰的字节大小为4,按照顺序,是从第9位开始存储,但是9不是4的整数倍,所以需要往后延到12,从12开始存储4个字节。所以c所占空间应该是[12,13,14,15];
  • short d:short修饰的字节大小为2,按照顺便从第16位开始存储,因为16正好是2的整数倍,所以可以从16位开始存。所以,d所占空间应该是[16,17]。
  • 收尾:通过计算,我们得到d所占空间的最后一个空间是17,在struct1中,double a8个字节,是最大成员,但是17不是8的整数倍,因此要补齐到24

同理,我们可以得到struct2的大小为16。

iOS 内存对齐_第3张图片
struct1.png

iOS 内存对齐_第4张图片
struct2.png

你可能感兴趣的:(iOS 内存对齐)