ios

iOS底层:内存对齐 (三)

2020-09-09  本文已影响0人  叶孤城1993

一、内存对齐规则:

二、案例和图示

上面的文本,比较枯燥,我们用图来讲解
定义两个结构体 structA 和 structB,我们就用上面的规则来猜这两个结构体在内存中如何开辟内存?内存占用多少?
任我们宰割的结构体先准备上

struct TestStructA {
    double  a; // 8 byte
    int     b; // 4 byte
    char    c; // 1 byte
    short   d; // 2 byte
} structA;


struct TestStructB {
    double  a; // 8 byte
    char    b; // 1 byte
    int     c; // 4 byte
    short   d; // 2 byte
} structB;

不清楚OC的基本数据类型在32位和64位上占用的,可以参考下面的表格


image.png

先看structA

struct TestStructA {
    double  a; // 8 byte
    int     b; // 4 byte
    char    c; // 1 byte
    short   d; // 2 byte
} structA;

1、a :根据规则1,structA的第一个成员a是double类型,偏移从0开始,a占用8个字节,range:【0,8】,如下:

a的布局

2、b 的内存占用,b是int类型,占4个字节,接着a从地址 8 开始,而8%4==0,七号起始地址可以整除当前要存的b的大小,所以b从8开始,占4个字节,range:【8,12】,如下:

image.png

3、c 是char,需要一个字节,继续b从地址12开始,12%1==0,所以c从12开始,占一个字节,range:【12,13】,如下:

image.png

4、终于轮到了d,继续c之后从13开始,d是short需要2个字节,但是13%2!=0 , 那就继续下一个位置14开始,14%2!=0, OK,我们就从14开始存,占2个字节,range:【14,16】,如下:

image.png

我们的c和d之间空了一个位置,也就是13,这个位置系统会自动补0.

根据规则三,当全部安排妥当,结构体的总大小是不是最大成员的整数倍,也就是整个结构体size % 最大成员的size == 0 可成立,如果不能整除,补0直到可以整除最大成员的size。structA很显然,刚刚好 16byte,最大成员a需要8byte,可以整除,所以,第4步就是structA的内存图示。

我们来验证一下,structA是不是占用了16个字节


image.png
image.png

再看structB

struct TestStructB {
    double  a; // 8 byte
    char    b; // 1 byte
    int     c; // 4 byte
    short   d; // 2 byte
} structB;

1、a需要8个字节,从0位置开始,和structA一样:

a的布局
2、b是char类型,需要一个字节,此时应该从位置8继续, 位置8 % b所需的1个字节 == 0,所以,从位置8开始,占一个位置,range:【8,9】
image.png
3、c是int,需要4个字节的位置,此时继续从9开始,显然9不能整除4,继续下一个位置10......直到12可以整除4,所以c从位置12开始,range:【12,16】
image.png
细心的朋友会发现:structA到位置16就已经结束了,structB的d还没开始计算。
4、d是short,需要2个字节,此时位置继续从16开始,16 % 2 == 0, 所以可以从位置16开始开辟,需要2个字节,所以range:【16,18】
image.png
这就结束了?????显然不是!structB总大小18 明显不能整除 最大成员a所需的8个字节,直到补0到位置24,才可以整除8。
image.png

最后,structB实际需要24个字节,我们来验证一下


image.png

structA和structB看似一样的写法,所需的内存空间却是大不相同

image.png
所以,了解内存对齐,是开发者的一项基本功。

问题来了,如果是下面的这种结构体,他的内存占用是什么样的?

struct TestStructC {
    char    *name;
    int     age;
} structC;

先查看结果他所占用的字节:


image.png

16个字节!

其实很好理解,char虽然只有一个字节,但是 name是个指针变量 ,指针作为一个变量,需要 8个字节 。name指向的具体内容才是1个字节。 所以在structC里,name指针变量才是成员。

当然了,最后检查一遍:整个结构体的size是不是最大成员size的整数倍!不是,要补全

image.png

引申:对象的大小呢?
我们在开发时,会定义 LYPerson *person = [LYPerson.alloc init];
本质上,person是一个指针,指向了这个对象在内存中的实际位置。 所以当我们sizeof(person)的时候,实际上,是获得这个指针变量的大小!指针需要8个字节!和person的属性内容无关!

image.png

三、了解了结构体的内存对齐,来看一看对象属性的字节对齐

定义了这么一个类

@interface LYPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
@property (nonatomic) char c3;
@property (nonatomic) char c4;
@end

初始化,先不给属性赋值

LYPerson *person = [LYPerson.alloc init];

我们来看一看person对象的内存分布
通过po person查看内存地址
然后使用 x/4gx 内存地址x/8gx 内存地址 分别查看4个数量和8个数量,每个分布需要8个字节,每一行有2个分布,也就是16字节。

image.png

每一行,如0x600000d72500: 0x00000001099a87a8 0x0000000000000000表示属性在内存中的地址,一行共16个字节。
我们打印看一下0x00000001099a87a8,只有他不为空

image.png

0x00000001099a87a8地址里的值是类名!一行总共16个字节,他就占了8个字节。

1、我们开始给属性赋值,并打上断点,先给name赋值,并查看内存变化:

LYPerson *person = [LYPerson.alloc init];
person.name      = @"一草一夜一孤城";
person.age       = 27;
person.c1        = 'a'; // ascii码 97
person.c2        = 'b'; // ascii码 98
person.c3        = 'A'; // ascii码 65
person.c4        = 'B'; // ascii码 66
image.png

0x6000029aad70出开始的16个字节区域内,多了一个子偏移地址:0x000000010d4e9038,里面存储值是属性name的值一草一叶一孤城

2、断电继续往下走,给age赋值,并查看内存变化:

image.png

有一个内存地址变成了0x1b,它里面存储了age 的内容 27

3、断点继续往下走,给c1赋值:

image.png
同样是在 0x6000029aad60区域,多了一个内存地址0x61,值是c1的‘a’。
4、断点往下走,给c2赋值:
image.png
它是紧挨着c1的地址0x61开始存,他是0x62,说明什么,char类型只占1个字节!

5、继续往下走,给c3赋值:

image.png

6、断点继续走,给c4赋值:

image.png

完成了,最终,成了这样:

image.png
分别存储了name、age、c1、c2、c3、c4,但是,却不是按我们赋值的顺序存放,这是因为,苹果为我们做了字节重排优化!
上一篇下一篇

猜你喜欢

热点阅读