什么是内存对齐?
内存对齐,简单来说就是编译器为了节省查找内存时间,通过一定的算法将数据按照规律存储,而不是一个一个的紧密排列,每个平台上的编译器都有自己的“对齐系数”(一般为4和8),而iOS的对齐系数为8即8字节对齐。
内存对齐的好处
CPU存取原理
CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。每次内存存取都会产生一个固定的开销,减少内存存取次数将提升程序的性能。所以 CPU 一般会以 2/4/8/16/32 字节为单位来进行存取操作。我们将上述这些存取单位也就是块大小称为(memory access granularity)内存存取粒度。
所以在读取对齐的内存效率更高,在读取未对齐的内存时,CPU需要做两次访问,而读取对齐的内存只需要访问一次,这大大提升了CPU的效率,提升了性能。
内存对齐规则:
【规则一】数据成员的对齐规则可以理解为min(m, n) 的公式, 其中 m表示当前成员的开始位置, n表示当前成员所需要的
位数。如果满足条件 m 整除 n (即 m % n == 0), n 从 m 位置开始存储, 反之继续检查 m+1位置 能否整除 所需的位数
n, 直到可以整除, 从而就确定了当前成员的开始位置。
【规则二】数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成.
员的内存大小,比如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8
【规则三】最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的需要补齐。
常见数据类型所占内存大小的表:
iOS获取内存大小的三种方式
sizeof: 是C/C++中的一个操作符(operator),简单的说其作用就是返回一个对象或者类型所占的内存字节数。在编译阶段 就会确定大小
class_getInstanceSize: 这个方法由runtime提供api,用于获取实例对象所占的内存大小,本质是获取实例对象中成员变量的内存大小
(注:如自定义类或继承关系 属性的多少变化而变化,如单纯的继承NSObject并且没有任何属性 打印为8 因为 有隐藏 isa 万物皆对象,而对象的本质为 继承 objc_object 的结构体 它就是对象模板)
malloc_size: 获取系统实际分配的内存大小(16字节对齐)
拓: 我们知道alloc 流程 最重要的三部曲
cls->instanceSize:计算需要开辟的内存空间大小(这里有一个算法为16字节对齐)
calloc:申请内存,返回地址指针
obj->initInstanceIsa:将 类 与 isa 关联
下面以一段代码为例
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *object = [[NSObject alloc]init];
NSLog(@"%lu - %lu - %lu",sizeof(object),class_getInstanceSize([NSObject class]),malloc_size((__bridge const void *)(object)));
}
return 0;
}
运行后的输出为:8 - 8 - 16
解释:
sizeof:可用于获取类、结构、共用体和其他用户自定义数据类型的大小。
class_getInstanceSize:返回类实例的大小。
malloc_size:返回实际给定的内存大小
结构体内存对齐
接下来,我们首先定义两个结构体,分别计算他们的内存大小,以此来引入今天的正题:内存对齐原理
//1、定义两个结构体
struct Struct1{
char a; //1字节
double b; //8字节
int c; //4字节
short d; //2字节
}Struct1;
struct Struct2{
double a; //8字节
int b; //4字节
short c; //2字节
char d; //1字节
}Struct2;
//计算 结构体占用的内存大小
NSLog(@"%lu-%lu",sizeof(Struct1),sizeof(Struct2));
以下是输出结果
从打印结果我们可以看出一个问题,两个结构体除了变量顺序一样之外,其他几乎完全一致,那么他们的大小为什么内存大小不相等
呢?这就是内存对齐
现象。