1. 内存对齐原理
上一篇提到了对象在alloc
创建时内存分配情况,
这一篇我们来更加深入的来研究一下什么叫内存对齐
1.1 准备工作
话不多说,为了方便直观的体现差异,定义一个宏,打印对象的 isa、内存地址、对象类型占用的内存大小
、对象实际占用的内存大小
、对象实际分配的内存大小
。一定要注意占用跟实际分配的区别,是不一样的。等一下看日志就一目了然。
照旧万年祖传观察对象日志代码
#define Log(_var) ({ NSString *name = @#_var; NSLog(@"%@: %@ -> %p || 对象类型占用的内存大小%lu, 对象实际占用的内存大小%lu, 对象实际分配的内存大小%lu ", name, [_var class], &_var, sizeof(_var), class_getInstanceSize([_var class]) ,malloc_size((__bridge const void*)(_var))); })
1.2 对象
昨天我们研究了人Person
类。今天不研究人,研究Dog
狗类🐶并且附上源代码已经运行结果日志
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Dog : NSObject
@end
@implementation Dog
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog = [Dog alloc];
Log(dog);
/*
* dog: Dog -> 0x7ffeefbff588 || 对象类型占用的内存大小8, 对象实际占用的内存大小8, 对象实际分配的内存大小16
*/
}
return 0;
}
我们这个无属性dog对象实际分配的内存大小16跟我昨天说的一样没有骗大家。这一点可以放心。因为是无属性对象实例,该对象只有isa指针所以只占用了8个大小的内存空间。
@property (nonatomic, strong) NSString *name;
如果我们给这个对象定义一个属性时打印如下
对象类型占用的内存大小8, 对象实际占用的内存大小16, 对象实际分配的内存大小16
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
如果我们给这个对象定义两个属性时打印如下
对象类型占用的内存大小8, 对象实际占用的内存大小24, 对象实际分配的内存大小32
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
如果我们给这个对象定义三个属性时打印如下
对象类型占用的内存大小8, 对象实际占用的内存大小24, 对象实际分配的内存大小32
我们发现随着属性的增加实际的分配内存大小符合昨天我们研究的对象创建16字节
对齐原则。
对象类型占用的内存大小
始终为8我猜测是对象指针的占用大小
对象实际占用的内存大小
属性以8字节对齐排列
我们来给dog属性赋值之后
Dog *dog = [Dog alloc];
dog.name = @"旺财";
dog.age = 2;
dog.height = 80;
x/4gx dog
一下看一下内存情况
0x104007b50: 0x001d800100002315 0x0000000200000001
0x104007b60: 0x0000000100001018 0x0000000000000000
我们发现 0x0000000100001018
是设置的name
, 0x0000005000000002
居然po不出来。
我们尝试po 0x00000050
是height
,po 0x00000002
是age
。原来height
跟age
共用了内存段节约内存
1.3结论
以上这个例子来进行说明 苹果中属性重排
,即内存优化
因为age跟height各占用8比较浪费。虽然分配了32。但是一共占用24。跟你定义属性的顺序无关。最终会按着最优的方式重新排列
2 结构体
对象研究完了。我们再来看一下结构体,看一下通过结构体的方式是否能验证些什么?
struct Dog1{
char a; //1字节
double b; //8字节
int c; //4字节
short d; //2字节
}Dog1;
struct Dog2{
double b; //8字节
int c; //4字节
short d; //2字节
char a; //1字节
}Dog2;
NSLog(@"狗一大小%lu--狗二大小%lu",sizeof(Dog1), sizeof(Dog2));
/*狗一大小24--狗二大小16*/
我们发现同是 4
个属性,同样包含char
,double
,int
,short
4个类型,只是顺序不同罢了。最后大小缺不一样
莫非就是我们之前说的内存对齐
?只是对象不需要你手动排列。而结构体需要你手动排列才生效,那么它的规则是什么呢?
内存对齐规则
一般内存对齐的原则主要有以下三点
1、数据成员的对齐规则可以理解为min(m, n)
的公式, 其中 m
表示当前成员的开始位置, n
表示当前成员所需要的位数。如果满足条件 m
整除 n
(即 m % n == 0
), n 从 m 位置开始存储, 反之继续检查 m + 1
能否整除n
, 直到可以整除, 从而就确定了当前成员的开始位置。
2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char
,int
,double
等元素,那b应该从8
的整数倍开始存储.)
3、结构体的总大小,也就是sizeof
的结果,.必须是其内部最大成员的整数倍.不足的要补⻬
我们通过简单的计算来验证一下
结构体Dog1
内存大小计算
根据内存对齐规则计算Dog1的内存大小,详解过程如下:
变量 a
:占 1
个字节,从0
开始,此时 min(0, 1)
,即 0
存储 a
变量 b
:占 8
个字节,从1
开始,此时 min(1, 8)
, 1
不能整除 8
,继续往后移动,知道 min(8, 8)
,从 8
开始,即 8-15
存储 b
变量 c
:占4
个字节,从16
开始,此时min(16, 4)
,16
可以整除 4
,即 16-19
存储 c
变量 d
:占2
个字节,从20
开始,此时min(20, 2)
,20
可以整除 2
,即 20-21
存储 d
因此 Dog1
的需要的内存大小为 15
字节,而 Dog1
中最大变量的字节数为 8
,所以 Dog1
实际的内存大小必须是 8
的整数倍,18
向上取整到 24
,主要是因为 24
是 8
的整数倍,所以 sizeof(Dog1)
的结果是 24
结构体 Dog2
内存大小计算
根据内存对齐规则计算 Dog2
的内存大小,详解过程如下:
变量 b
:占 8
个字节,从 0
开始,此时 min(0, 8)
,即 0-7
存储 b
变量 c
:占 4
个字节,从 8
开始,此时 min(8, 4)
, 8
可以整除 4
,即 8-11
存储 c
变量 d
:占 2
个字节,从 12
开始,此时 min(12, 2)
, 20
可以整除2,即 12-13
存储 d
变量 a
:占 1
个字节,从 14
开始,此时 min(14, 1)
,即 14
存储 a
因此Dog2
的需要的内存大小为 15
字节,而 Dog2
中最大变量的字节数为 8
,所以 Dog2
实际的内存大小必须是 8
的整数倍, 15
向上取整到 16
,主要是因为 16
是 8
的整数倍,所以 sizeof(Dog2)
的结果是 16
不知道谁最大?我们来先看一个表,下表是对不同数据类型所占用的内存大小(纯手打,可复制)
有需要的点个关注。评论查眼👀Mark
,老司机开车从此不迷路
C | OC | 32位 | 64位 |
---|---|---|---|
bool | BOOL(64位) | 1 | 1 |
signed char | (__signed char)int8_t、BOOL(32位) | 1 | 1 |
unsigned char | Boolean | 1 | 1 |
short | int16_t | 2 | 2 |
unsigned short | unichar | 2 | 2 |
int int32_t | boolean_t(32位)、NSInteger(32位) | 4 | 4 |
unsigned int | boolean_t(64位)、NSUInteger(32位) | 4 | 4 |
long | NSInteger(64位) | 4 | 8 |
unsigned long | NSUInteger(64位) | 4 | 8 |
long long | int64_t | 8 | 8 |
float | CGFloat(32位) | 4 | 4 |
double | CGFloat(64位) | 8 | 8 |