iOS底层:内存对齐 (三)

一、内存对齐规则:

  • 【规则一】数据成员的对齐规则可以理解为min(m, n) 的公式, 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0), n 从 m 位置开始存储, 反之继续检查 m+1位置 能否整除 所需的位数n, 直到可以整除, 从而就确定了当前成员的开始位置。

  • 【规则二】数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8

  • 【规则三】最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的需要补齐。

二、案例和图示

上面的文本,比较枯燥,我们用图来讲解
定义两个结构体 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,但是,却不是按我们赋值的顺序存放,这是因为,苹果为我们做了字节重排优化!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270