注册
iOS

iOS底层-内存对齐

一、什么是内存对齐?


我们先看下以下例子:


struct struct0 {
int a;
char c;
}s;

NSLog(@"s : %lu", sizeof(s)); //输出8

32位下int4bytechar1byte,那么放到结构体应该是4+1=5byte啊,但实际得到的结果却是8,这就是内存对齐导致的。



元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。 从结构体存储的 首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小(通常它为4或8)来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始,这就是所谓的内存对齐



二、为什么要进行内存对齐?


1.平台限制



  • 各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取,它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存。

2.性能原因



  • 例如在32位下的int型变量,如果按照对齐的方式,读一次就可以读出,如果忽略对齐,int存放在奇数位或者不在4的倍数位上,则需要读两次,再进行拼接才能得到数据。

    • 例如,没有内存对齐机制,一个int变量从地址1开始存储,如下图:



截屏2021-06-09 11.51.18.png
这种情况,处理器需要:剔除不要的,再合并到寄存器,做了些额外的操作。而在内存对齐的环境中,一次就可以读出数据,提高了效率


三、内存对齐规则


1. 概念:



  • 每个特定平台上的编译器都有自己的默认对齐系数(也叫对齐模数)。可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数,这个n就是要制定的对齐系数
  • 有效对齐值:给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位
  • Xcode中默认#pragma pack(8),也就是8字节对齐

2. 规则:



  • (1): 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节
  • (2): 结构体的总大小为有效对齐值的整数倍,如有需要编译器会在最末一个成员之后加上填充字节

3.举例:


结构体


环境(Xcode 12.3)

例一


struct Struct1 {
double a;
char b;
int c;
short d;
}s1;

分析



  • 对齐系数为:pack(8),也就是8字节,最长的数据类型是:double,也是8字节,则 有效对齐值min(8,8) = 8
  • 根据规则(1)(2),可得出:

截屏2021-06-09 14.45.19.png



每个成员的大小都要和有效对齐值对比,取比较小的数,然后这个数的整数倍作为这个成员变量的offset起始存储位置,如果中间不是整数倍,则需要填充字节。成员变量存储完后,如果整体大小不是有效对齐值的整数倍,则需要在 末尾填充字节 至整体大小为有效对齐值的整数倍为止。



分析的结果是 24字节,我们打印来验证下:


截屏2021-06-09 13.25.26.png


打印的结果和我们分析的一样~


例二


struct Struct2 {
double a;
int b;
char c;
short d;
}s2;

printf("\n Struct1 size: %lu\n", sizeof(s2)); // 输出 16


这两个结构体成员变量都是一样的,只不过位置换了下,怎么内存大小也变了?啊这...



我们继续来分析下:


截屏2021-06-09 14.45.43.png
分析得到的结果是16字节,我们再来打印验证下:

截屏2021-06-09 13.40.57.png


原来结构体成员变量的顺序对内存也是有影响的


例三


struct Struct3 {
double a; // 8 [0,7]
int b; // 4 [8,11]
char c; // 1 [12]
short d; // 2 (13, [14,15]
int e; // 4 [16, 19]
struct Struct1 {
double a; // 8 (20, 21, 22, 23, [24, 31]
char b; // 1 [32]
int c; // 4 (33, 34, 35 [36, 39]
short d; // 2 [40, 41],42, 43, 44, 45, 46, 47, 48
}s1;
}s3;

分析



  • 对齐系数为:pack(8) = 8Struct1中最大为8字节Struct3其他的成员最大为8字节, 则Struct3 有效对齐值min(pack(8),8) = 8

截屏2021-06-09 17.02.54.png
打印验证下:


截屏2021-06-09 15.30.12.png

结果是正确的~


可能存在的误区


  1. offset:计算成员的offset时,有可能会将成员大小的整数倍当做其offset,这个是错误的,offset是由有效对齐值成员大小 二者的最小值的整数倍决定的
  2. 整体大小:整体大小(如果需要补充字节,就是补充后的)是最大成员的整数倍,这是不准确的。整体大小是有效对齐值的整数倍

我们在举一个例子:


32位下


// 32 位下对齐系数 pack(4)
struct Struct4 {
int a; // 4 [0,3]
double b; // 8 [4,11]
}s4;

printf("\n\n Struct4 size: %lu\n", sizeof(s4)); // 输出 12

64位下


// 64 位下对齐系数 pack(8)
struct Struct4 {
int a; // 4 [0,3]
double b; // 8 (4, 5, 6, 7, [8, 15]
}s4;

printf("\n\n Struct4 size: %lu\n", sizeof(s4)); // 输出 16

打印如下:


image_2021-06-09_17-35-22.png




  • 32位中,Struct4有效对齐值min(pack(4), 8) = 4a从首地址开始,boffsetmin( min(pack(4), 8), 8) = 4,所以得出结果为12

  • 64位中,Struct4有效对齐值min(pack(8), 8) = 8a从首地址开始,boffsetmin( min(pack(8), 8), 8) = 8,所以得出结果为16


总结


  • 计算成员的offset是由有效对齐值成员大小二者的最小值的整数倍决定的。
  • 整体大小(如果需要补充字节,就是补充后的)是有效对齐值的整数倍。
  • 结构体成员的顺序不同会导致内存不同,而对象的本质就是一个结构体,我们可以调整对象属性的位置来达到内存优化的目的。

对象


获取对象内存大小的方式


  • sizeof
  • class_getInstanceSize
  • malloc_size

sizeof


  1. sizeof运算符,而不是一个函数
  2. sizeof传进来的是类型,用来计算这个类型占多大内存,这个在编译器编译阶段就会确定大小并直接转化成8 、16、24这样的常数,而不是在运行时计算。参数可以是数组、指针、类型、对象、结构体、函数等。
  3. 它的功能是:获得保证能容纳实现所建立的最大对象的字节大小

class_getInstanceSize


  • class_getInstanceSizeruntime提供的api,计算对象实际占用的内存大小,采用8字节对齐的方式进行运算。

malloc_size


  • malloc_size用来计算对象实际分配的内存大小,这个是由系统采用16字节对齐的方式运算的

可以通过下面代码测试:


@interface LGPerson : NSObject

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

@end
// LGPerson 中有4个属性 NSString *name,NSString *nickName,int age,long height
int main(int argc, const char * argv[]) {
LGPerson *person = [LGPerson alloc];
person.name = @"Cc";
person.nickName = @"KC";

NSLog(@"\n%lu\n - %lu\n - %lu\n", sizeof(person), class_getInstanceSize([LGPerson class]), malloc_size((__bridge const void *)(person)));
// 输出 8, 40, 48
return 0;
}

链接:https://juejin.cn/post/6971750424198152200

0 个评论

要回复文章请先登录注册