iOS中的内存对齐

内存对齐应该是编译器的管辖范围。编译器为程序中的每个数据单元安排在适当的位置上。

对齐原因:

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

规则定义:

  1. 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
  2. 结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
  3. 结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

规则解析:

  1. 规则1中表明数据成员的存放是按照定义的顺序依次存放的
  2. #pragma pack是对齐系数,每个平台不一样,程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数(32位平台一般为4,64位平台一般为8)。iOS下默认为8。这个数值大家可以通过调试#pragma pack(n)测试验证得到。
  3. 规则1,当第x(x>1)个成员y存放的时候,y按照min(n,m)来对齐存放,其中n为对齐系数,m为成员y的数据类型长度。
  4. 在完成各个数据成员的存放排列后,通过规则2,取min(n,maxM)进行对齐,其中n为对齐系数,maxM为所有数据成员类型中长度的最大值。

基础知识:

1.一个字节包含8个二进制位
2.一个十六进制位可用4个二进制位表示
3.一个字节可以由2个十六进制位表示

  1. 0x0000 0000 0000 0008表示16个16进制位,可以表示8个字节
    所以8可以用8个字节0x0000 0000 0000 0008表示,或者4个字节0x0000 0008,或者2个字节0x0008,取决于定义8的数据类型。
  2. 字符'a'换成ASCII码为97,可以用 0x61表示。
  3. iOS系统的编译平台是按照小端法进行编译。

实例分析:

struct Person {
    char a;
    long b;
    int c;
    short d;
}MyPerson;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MyPerson.a = 'a';
        MyPerson.b = 8;
        MyPerson.c = 4;
        MyPerson.d = 2;
        NSLog(@"Adress=======MyPerson:%p",&MyPerson);  
        NSLog(@"Size=======MyPerson:%lu",sizeof(MyPerson));

    }
    return 0;
}

第一个成员char类型的成员a='a'占用1字节,此时:
a: 0x61

第二个成员long类型的成员b=8占用8个字节,根据规则解析3,b=8按照min(8,8)=8对齐,b的起始位置为8的倍数,不满足,a需要补齐7个字节保证b的起始位置为8的倍数
此时:
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008

第三个成员int类型的成员c=4占用4个字节,根据规则解析3,整数c=4需要按照min(8,4)=4进行对齐,c的起始位置需要为4的整数倍,现在已经满足
此时:
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008
c:0x0000 0004

第四个成员short类型的整数d=2占用2个字节,根据规则解析3,d按照min(8,2)=2进行对齐,d的起始位置需要为2的整数倍,现在已经满足
此时:
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008
c:0x0000 0004
d:0x0002

根据规则解析4,结构体需要进行整体对齐,取min(n,maxDataLength) = max(8,8) = 8对齐,现在为8+8+4+2=22字节,需要补2个字节,按照排列顺序,在d占用内存段补2个字节;

最后得到
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008
c:0x0000 0004
d:0000 0002
其中我们看把c和d看成共占用一段8字节的内存,因为对齐系数为8,编译器按照8的整数倍来读取内存地址。

按照小端法进行修正,此时内存排列应该内应该是
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008
dc:0x0000 0002 0000 0004,
其中dc:0x0000 0002 0000 0004的第1-8位表示成员d的值,右边第9-16位表示成员c的值

综上,MyPerson结构体整体占用8+8+8=24字节


iOS中的内存对齐_第1张图片

结构体嵌套结构体:

struct MyPerson1{
    char a;
    long b;
    int c;
    short d;
}MyPerson1;

struct MyPerson2{
    char a;
    long b;
    int c;
    short d;
    struct MyPerson1 person; 
}MyPerson2;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MyPerson1.a = 'a';
        MyPerson1.b = 8;
        MyPerson1.c = 4;
        MyPerson1.d = 2;
        
        MyPerson2.a = 'a';
        MyPerson2.b = 8;
        MyPerson2.c = 4;
        MyPerson2.d = 2;
        MyPerson2.person = MyPerson1;
        
        NSLog(@"Adress=======MyPerson1:%p",&MyPerson1);
        NSLog(@"Adress=======MyPerson2:%p",&MyPerson2);
        NSLog(@"Size=======MyPerson2.person:%lu", sizeof(MyPerson2.person));
        NSLog(@"Size=======MyPerson2:%lu", sizeof(MyPerson2));

    }
    return 0;
}

输出如下:


iOS中的内存对齐_第2张图片

person是一个结构体,最大成员为long类型,长度为8,所以按照min(8,8) 来对齐存放,已满足条件,所以直接存储即可,因此MyPerson2结构体整体占用48字节。

类的内存对齐:

定义一个类

@interface Teacher : NSObject

@property (nonatomic, assign) char a;
@property (nonatomic, assign) long b;
@property (nonatomic, assign) int c;
@property (nonatomic, assign) short d;

@end

并在main.m中添加如下代码:

Teacher *t = [[Teacher alloc] init];
t.a = 'a';
t.b = 8;
t.c = 4;
t.d = 2;

NSLog(@"Adress=======t:%p",t);

输出如下:


iOS中的内存对齐_第3张图片

可以看到,该对象的第一个八字节存储着isa的值,而对象的第2个八字节和第3个八字节这2个内存段存储了我们定义的成员a、b、c、d(准确表述为_a,_b,_c,_d)的值,说明编译器做了相应的优化,不会直接按照我们在类中定义成员的顺序生成构造对应的结构体。

class_getInstanceSize和malloc_size

class_getInstanceSize:依赖于,返回创建一个实例对象所需内存大小,不考虑malloc函数的话,内存对齐一般是以 8 字节对齐。
malloc_size:依赖于,返回系统实际分配的内存大小,在Mac、iOS中的malloc函数分配的内存大小总是 16 的倍数。

新建一个类XYPerson继承于NSObject:

@interface XYPerson : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) long height;
@property (nonatomic, assign) char c1;
@property (nonatomic, assign) float m_float;
@end
​```
在main函数中写上如下代码:
​```
#import "XYPerson.h"
#import 
#import 
​
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        XYPerson *p = [[XYPerson alloc] init];
        p.name = @"Ring";      //  NSString   8
        p.age = 18;            //  int        4
        p.height = 188;        //  long       8
        p.c1 =  'a';           //  char       1
        p.m_float =  12.6;     //  float      4
        NSLog(@"申请内存大小为:%lu——-系统开辟内存大小为:%lu", class_getInstanceSize([p class]), malloc_size((__bridge const void *)(p)));
    }
    return 0;
}​

输出如下:


iOS中的内存对齐_第4张图片

iOS中,对象的属性是8字节对齐,而对象是16字节对齐。
因为内存是连续的,通过 16 字节对齐规避风险和容错,可以防止访问溢出。同时,也提高了寻址访问效率,也就是空间换时间。

·

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