iOS内存对齐分析

什么是内存对齐?

  • 在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.

为什么要内存对齐?

  • 1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  • 2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

内存对齐原则

  • 1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。
  • 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从 其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b 里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
  • 收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大 成员的整数倍.不足的要补⻬

对象内存地址分析

在了解OC对象地址前我们首先看一下不同数据类型所占据的内存地址大小


不同数据类型内存地址大小

在了解不同数据类型所占的内存大小后我们首先来看一下一个OC对象的内存地址情况,我们生命一个对象,它有如下属性

@interface SYPerson : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *sex;
@property (nonatomic) int age;
@property (nonatomic) int height;

@end

调用,给属性赋值并打印person对象的内存大小

    SYPerson *person = [[SYPerson alloc]init];
    person.name = @"Syong";
    person.sex = @"man";
    person.age = 17;
    
    NSLog(@"%@",person);
    NSLog(@"person对象实际需要的内存大小: %zd", class_getInstanceSize([person class]));
    NSLog(@"person对象实际分配的内存大小: %zd", malloc_size((__bridge const void *)(person)));

打印结果

打印结果

我们发现person对象内存大小一共分配了48字节,实际使用了40字节,那么这些内存地址到底是如何排列的呢?根据内存对齐的规则,我们得到下图所示的排列情况:
内存地址排列情况

  • 首先Person作为一个NSObject对象,本身的isa占有8字节,首地址从0开始排,那么地址情况就是[0,1,2,3,4,5,6,7]
  • 后面的数据成员存储首地址根据起始位置的isa大小的整数倍来排列,那么就是从8开始排,name作为NSString类型占8字节,即[8,9,10,11,12,13,14,15],同理后面的sex为[16,17,18,19,20,21,22,23]
  • age作为Int类型占有4字节,那么就是[24,25,26,27]
  • height作为long类型占有8字节,但是要从8的整数倍开始,27过后就是32是8的整数倍,那么就是从32开始排[32,33,34,35,36,37,38,39]
  • 到这里为止person对象所有占有的内存大小是40,而person对象总大小必须是内部最大成员的整数倍,也就是8的整数倍即40

那么这就出现了一个问题,为什么明明内存实际使用了40,从前面推算分配的内存也应该是40,但是为什么最后分配的内存结果确实48呢?

具体原因是Apple系统中的malloc函数分配内存空间时,内存是根据一个bucket的大小来分配的。bucket的大小是16,32,48,64,80 ...,可以看出系统是按16的倍数来分配对象的内存大小的

我们可以将person对象的所有属性注释,然后再打印person对象的内存大小来验证这个原因是否正确,得到下面结果:

无成员变量的`person`内存大小

或者我们只保留name和age两个属性再打印结果:
只保留name和age

这也验证了前面说法,虽然没有任何属性的person只有isa占据了8字节,但是分配了16字节;保留nameageperson实际使用24字节却分配了32字节。所以虽然前面内存只使用了40,按理说也应该分配40字节,但是因为系统按照最小16字节的bucket分配内存,那么根据内存对齐,补位就应该是分配48
通过前面的论证也证实了字节对齐的规则,其实我们也可以简化一下,对象本身就是结构体,也可以通过结构体来进行验证,这里我就不再使用结构体来复述一遍了,下面我们只看一种情况,那就是结构中嵌套结构体的时候,内存对齐是什么一个情况。

嵌套结构体的内存对齐

比如有这么两个结构体

#import 

struct SYStruct {
    int a;
    double b;
}struct1;

struct LGStruct {
    char a;
    int b;
    struct SYStruct c;
}struct2;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
    }
    return 0;
}
//输出结果:
//2020-09-08 23:42:03.207571+0800 001-内存对齐原则[12425:218745] 16-24
  • 结构体struct1里面有4字节int和8字节的doubule,a内存排列情况为[0,1,2,3],b从8的整数倍开始则为[8,9,10,11,12,13,14,15],那么一共是16个字节,满足最大成员double类型8的整数倍
  • 结构体struct2的a地址为[0],b[4,5,6,7],c作为一个结构体,其本身最大的成员大小为double类型的8,那么c为[8,9,10,11,12,13,14,15],根据结构体总大小必须是内部成员大小的整数倍,那么可得结构体struct2的大小应该为24。

总结

以上就是对内存对齐的一些分析,简单来说通过内存对齐可以达到更快的读取以及通过预留空间在读取数据时更加安全,通过牺牲一点内存的大小达到更快更强的目的。

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