OC底层原理02—内存对齐

iOS--OC底层原理文章汇总

为什么要内存对齐?

  • 1.性能方面:提升CPU读取速度。CPU是按照块来读取的,块的大小可以为2,4,8,16。块的大小也称为内存读取粒度。假设某平台硬件CPU读取粒度为8,如果没有内存对齐:
    1)CPU从0-7读取数据,正常取出;
    2)如果数据从第2字节开始,CPU先读取0-7,再读取8-15字节的数据,然后将2-10字节的数据组合为正确的数据返回。
    这样就导致CPU进行了两次读取,效率很低。如果是数据的内存是对齐,CPU可一次性的读取出数据,虽然数据占用多余的内存空间导致一些内存碎片化,但是通过内存空间换取速度的方式是非常可取的。现在的手机、电脑也是通过扩容内存来换取读写速率的提升。
  • 2.平台硬件:不同的硬件平台对任意地址的数据读取不一,不是所有的硬件平台都可以访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些类型的数据,否则抛出硬件异常。

内存对齐原理

万物都有规则,内存对齐也有其规则:
NO.1:数据成员对⻬规则。结构(struct)(或联合(union))的数据成员,第
⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要
从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存储。
NO.2:结构体作为成员。如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储。(若struct a⾥存有struct b,b⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)
NO.3:结构体的总⼤⼩(sizeof的结果)必须是其内部最⼤成员的整数倍。不⾜的要补⻬。
现在来看看结构体内存的计算和对齐:
假设有两个结构体

struct StructOne{
  double a; // 8字节
  int    b; // 4字节
  short   c; // 2字节
  char   d; // 1字节
}structOne;

struct StructTwo{
  int    a; // 4字节
  double b; // 8字节
  char   c; // 1字节
  short   d; // 2字节
}structTwo;

其中结构体指针长度为8,结构体的内存尺寸则是其内部属性的集合。
sizeof(structOne) = 16;sizeof(structTwo) = 24
结果有点小惊喜和意外,都是一样的成员,因为排序不一样,其内存大小也不同。这是系统按照内存对齐做了“优化”的结果。
对照对齐规则:

structOne的字节分析

上图中是对structOne所占字节分析,该类型占用大小为[0,14],安卓内存对齐原则,其类型所需要的内存大小为最大成员字节大小的整数倍,故而16满足0-14的内存需求。
同理可得sizeof(structTwo)的内存所占位[0,18],共19个长度,安装规则其需求为24字节。

structTwo的字节分析

如果一个结构体嵌套了structOne组成了,按照规则structOne将为structThree中最大的内存成员,其中structOne中最大成员为double类型字节数为8,structThree实际所要的内存空间分别为:
int a; -> [0,1,2,3],
double b; -> 4,5,6,7,[8,9,10,11,12,13,14,15],
struct StructOne one; -> [16,17...30,31]
共占用32个字节,
最大成员内存8的最小整数倍为32,所要structThree的内存空间大小为32。

struct StructThree{
  int    a; // 4字节
  double b; // 8字节
  struct StructOne one; // 16
}structThree;

拓展:内存计算

定义一个Book类,有以下属性,并在VC中实例化它。

// Book.h
@interface Book : NSObject

@property (nonatomic,copy) NSString * bookName;

@property (nonatomic,copy) NSString * bookAuth;

@property (nonatomic,assign) float  bookVersion;

@property (nonatomic,assign) int  pageNnumbers;

@end
   // 在VC中调用
   Book * book = [[Book alloc] init];
   book.bookName = @"Objective-C Programming";
   book.pageNnumbers = 256;
  
   NSLog(@"\nbook:%@ \nsizeof: %lu \nclass_getInstanceSize:%lu \nmalloc_size: %lu",book,sizeof(book),class_getInstanceSize([book class]),malloc_size((__bridge const void *)(book)));

执行会得到

2020-09-09 17:48:23 OC_MemoryAlignment[44017:1549093] 
book: 
sizeof: 8 
class_getInstanceSize: 32 
malloc_size: 32
  • sizeof:是一个运算符,计算的是括号中类型占用内存的大小。此例中book为一个指针类型,故而大小为8;
  • class_getInstanceSize:源码如下:
/** 
 * Returns the size of instances of a class.
 * 
 * @param cls A class object.
 * 
 * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
 */
OBJC_EXPORT size_t
class_getInstanceSize(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

它是runtime中的一个方法,获取的是类实例实际占用内存大小(字节单位)。Book中定义了4个属性,class_getInstanceSize计算就是Book类实例四个属性实际占用内存大小为32字节。(1个属性对应8个字节大小)

  • malloc_size源码中这样写道:
extern size_t malloc_size(const void *ptr);
    /* Returns size of given ptr */

它返回的则是实际类实例开辟的内存空间大小。

总结
sizeof-> 类型占用内存的大小
class_getInstanceSize -> 实际占用内存大小(字节单位)。
malloc_size->实际开辟的内存空间大小。

各类型所占内存空间


你可能感兴趣的:(OC底层原理02—内存对齐)