美文网首页
iOS中的内存对齐

iOS中的内存对齐

作者: ssRing | 来源:发表于2020-09-08 20:30 被阅读0次

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

对齐原因:

  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字节


结构体嵌套结构体:

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;
}

输出如下:



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);

输出如下:



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

class_getInstanceSize和malloc_size

class_getInstanceSize:依赖于<objc/runtime.h>,返回创建一个实例对象所需内存大小,不考虑malloc函数的话,内存对齐一般是以 8 字节对齐。
malloc_size:依赖于<malloc/malloc.h>,返回系统实际分配的内存大小,在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 <objc/runtime.h>
#import <malloc/malloc.h>
​
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中,对象的属性是8字节对齐,而对象是16字节对齐。
因为内存是连续的,通过 16 字节对齐规避风险和容错,可以防止访问溢出。同时,也提高了寻址访问效率,也就是空间换时间。

·

相关文章

  • iOS底层探究 - 内存对齐

    目录1:内存对齐的原因2:内存对齐的规则3:结构体内存分配演练以及在iOS中对象成员的内存分配探索 一 :内存对齐...

  • iOS内存对齐

    这篇文章我们来探索一下iOS内存对齐的原理,在探索完内存对齐原理之后,你就会明白内存对齐的好处。 在讲述内存对齐时...

  • iOS中的内存对齐

    一、什么是内存对齐 内存对齐是一种在计算机内存中排列数据(表现为变量的地址)、访问数据(表现为CPU读取数据)的一...

  • iOS中的内存对齐

    内存对齐应该是编译器的管辖范围。编译器为程序中的每个数据单元安排在适当的位置上。 对齐原因: 平台原因(移植原因)...

  • iOS-底层原理 05:内存对齐原理

    iOS 底层原理 文章汇总 在探讨内存对齐原理之前,首先介绍下iOS中获取内存大小的三种方式 获取内存大小的三种方...

  • iOS底层之内存对齐算法解析

    目前但凡一个iOS岗面试都会问个内存对齐问题,那么什么是字节对齐?成员变量对齐和对象内存对齐有什么区别?今天我来为...

  • iOS之内存对齐

    关于iOS的内存对齐,首先我们思考一个问题,iOS的对象实例在内存中是如何分布的?带着这个问题我们往下看。 OC对...

  • iOS 内存对齐

    在上一边文章中,我们在简单介绍内存对齐,今天我们更加深入一点: 一、获取内存大小的三种方式 先看下面这段代码: 看...

  • iOS - 内存对齐

    1、内存对齐是什么? 在计算机中,内存大小的基本单位是字节,理论上来讲,可以从任意地址访问某种基本数据类型。 但是...

  • iOS 内存对齐

    前言 现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是...

网友评论

      本文标题:iOS中的内存对齐

      本文链接:https://www.haomeiwen.com/subject/icepsktx.html