iOS底层探索 02-内存对齐原理

一. 结构体引入

结构体是由基本数据类型或者指针,数组,结构体/联合体/枚举等类型组成的一种结构,我们可以简单地定义一个结构体如下

struct Person {
  char *contact;
  bool sex;
  short int age;
  int height;
  char  name[9];
};

二. 结构体所占内存大小

结构体的大小主要与操作系统地址总线宽度编译器对齐方式相关。

各种数据类型在ios中的占用内存大小


image

在64位系统下探索结构体占用的内存大小


    struct  Person stu;
    stu.sex = NO;
    stu.age = 18;
    stu.contact = "984361822@qq.com";
    stu.height = 20;
    stu.name[0]= 65;
    stu.name[1]= 66;
    stu.name[2]= 65;
    stu.name[3]= 66;
    stu.name[4]= 65;
    stu.name[5]= 66;
    stu.name[6]= 65;
    stu.name[7]= 66;
    stu.name[8]= 65;
    printf("%lu",sizeof(stu)); //32

上面代码打印出的stu所占内存为32个字节,

\color{#FF0000}{为啥是32个字节?}

首先将断点设置在如图位置,当断点断住时,在调试区域可以看到stu对象,右击stu,选中View Memory of "stu",可以查看stu的内存分配


image

从内存地址中的值,可以观察到stu的内存分配:


image

成员变量在内存中的存储位置为:


image
  • contact; 是个指针,在64位系统下占用8个字节

  • sex,age,height 共同占用8个字节
    sex;实际占用一个字节,但需要字节对齐,sex后会空一个字节
    age 占用2个字节,
    height;占用8个字节

  • name 实际占用9个字节,但会字节对齐,其后会预留7个字节,以达到8字节的对齐

\color{#FF0000}{为啥编译器会字节对齐?}

  • 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

  • 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

三. 结构体嵌套的内存大小


struct Age {
    int age;
    bool sex;
};
struct Human {
    char  name;
    struct Age a1;
};

结构体Age的占用的内存大小在64位系统下是8个字节,占用内存最大的成员变量是age,占用4个字节;
结构体Human的尺寸在64位系统下占用12个字节,由于结构体Age中最大的成员变量是age,占用4个字节;所以name占用一个字节后,将空三个自己,然后再开始存储a1;

\color{#FF0000}{总结:内存对齐原则}

  • 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始

比如int为4字节,则要从4的整数倍地址开始存
储。

min(当前开始的位置m,n)m=9n=4 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0), n 从 m 位置开始存储, 反之继续检查 m = m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置。

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

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

案例分析:


image

四. OC类的内存优化

定义一个自定义CJLPerson类,并定义几个属性,
@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;

@property (nonatomic) char c1;
@property (nonatomic) char c2;
@end

@implementation LGPerson

@end

在OC类中声明的实体属性会转化为数据成员。每个OC类中还会有一个隐式的数据成员isa,这是一个指针类型的数据成员,并且是作为类的第一个数据成员被定义。

OC类中定义的实例属性,系统在编译时会创建一个带下划线的数据成员,属性数据成员的内存排列顺序会被优化处理。
存储顺序为: isa,c1,c2,age,name,nickname,height

直接定义内部的数据成员不参与排序优化,比如下面的形式:


@implementation LGPerson {
   //内部的数据成员
    int type;
    NSString  *sencondName;
}
@end

上面的实现中定义了两个内部数据成员type,sencondName。当出现这种情况时编译器不会对这些内部数据成员的顺序进行优化,而是按定义的顺序在内存中进行排列,并且是优先于属性数据成员进行排列,type和sencondName排列在isa之后,其他实例属性之前.

存储顺序为: isa,type,sencondName,c1,c2,age,name,nickname,height
注意:type会占用8个字节

五. iOS中获取内存大小的三种方式

  • sizeof:操作符,传入的可以是数据类型和对象,编译阶段就可以确定其大小
  • class_getInstanceSize:用于获取类的实例对象所占用的内存大小,并返回具体的字节数,其本质就是获取实例对象中成员变量的内存大小,obj4中会进行8字节对齐
/** 
 * 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);



size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}



// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}



static inline uint32_t word_align(uint32_t x) {
    //8字节对齐 #define WORD_MASK 7UL
    return (x + WORD_MASK) & ~WORD_MASK;
}


  • malloc_size:获取系统实际分配的内存大小,64位系统下会进行16字节对齐

六. 内存对齐

6.1#pragma pack(n)

每个平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,在ios中,Xcode默认为#pragma pack(8),即8字节对齐
通过预编译命令#pragma pack()取消自定义字节对齐方式

#pragma pack(1) //编译器1字节对齐存储
struct Person
{
char sex;
short age;
float weight;
char c1
};
#pragma pack() //取消1字节对齐,恢复默认对齐
//sizeof(Person)  = 8

struct Human
{
char sex;
short age;
float weight;
char c1;
};

//sizeof(Human) = 12

6.2 __attribute__((aligned (n)))

__attribute__((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

__attribute__((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

#define PACKED __attribute__((packed))
struct PACKED Person {//取消字节对齐
    char sex;
    short age;
    float weight;
};
#pragma pack() //恢复默认对齐
//sizeof(Person)  = 7 ,其中sex占一个字节,age占两个字节,weight占4个字节

#define PACKED8 __attribute__((aligned (8)))
struct PACKED8 Student{
    char sex;
    short age;
    float weight;
    char c1;
};
#pragma pack() //恢复默认对齐
//sizeof(Student)  = 16  其中sex占一个字节,空隙1个字节,age占两个字节,weight占4个字节,c1占一个字节,末尾空隙7个字节


struct Human{
    char sex;
    short age;
    float weight;
    char c1;
};
    //sizeof(Human) = 12 ,其中sex占一个字节,空隙1个字节,age占两个字节,weight占4个字节,c1占一个字节,末尾空隙三个字节

可以定义一个Student对象来断点查看下内存:

 struct Student stu;
    stu.sex = 'M';
    stu.age = 18;
    stu.weight= 50.0;
    stu.c1 = 'A';

内存的存储情况如下:sex占一个字节,空隙1个字节,age占两个字节,weight占4个字节,c1占一个字节,末尾空隙7个字节
[图片上传失败...(image-b91ff4-1600327993148)]

6.3 16字节对齐算法1

static inline size_t align16(size_t x) {
   return (x + size_t(15)) & ~size_t(15);
}

6.4 16字节对齐算法2

#define SHIFT_NANO_QUANTUM      4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)   // 16

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

6.5 8字节对齐算法

static inline uint32_t word_align(uint32_t x) {
  // 8字节对齐 #define WORD_MASK 7UL
    return (x + WORD_MASK) & ~WORD_MASK;
}


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