本章主要由结构体内存对齐到苹果的属性重排, 以及
16字节对齐算法
0x00 -- 获取内存大小的三种方式
LGPerson *person = [LGPerson alloc];
person.name = @"Cooci";
person.nickName = @"KC";
NSLog(@"%@ - %lu - %lu - %lu",person,sizeof(person),class_getInstanceSize([LGPerson class]),malloc_size((__bridge const void *)(person)));
// 输出 8 - 40 - 48
获取内存大小的三种方式:
sizeof(expression-or-type)
小提示 : sizeof
的三种语法形式:
int a = 10;
size_t a1 = sizeof(a); // 4
size_t a2 = sizeof a; // 4
size_t a3 = sizeof(int); // 4
int *pa = &a;
size_t p1 = sizeof(pa); // 8
size_t p2 = sizeof pa; // 8
size_t p3 = sizeof(int *); // 8
NSObject *obj = [NSObject alloc] ;
size_t o1 = sizeof(obj); // 8
size_t o2 = sizeof obj; // 8
size_t o3 = sizeof(NSObject*);// 8
sizeof()
是操作符,不是函数;其作用是返回一个对象或者类型所占的内存字节数。基本数据类型
int
,char
,double
,float
等这样简单内置数据类型,它们的大小和系统相关, 不同系统下取值也不一样。如果是结构体,
sizeof
涉及到字节对齐的问题,根据计算机组成原理
教导我们这样有助于加快计算机的取数速度,否则多话指令周期。让宽度为2
的节本数据类型short等
,都位于能被2
整除的低智商;让宽度为4
的基本数据类型int等
,都位于能被4
整除的地址上,以此类推,这样,俩个数中间就可能需要加入填充的字节
,所以整个结构体的sizeof
值就增长了。详细对齐规则看文章下方结构体对齐
。 这里不再赘述。
class_getInstanceSize
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());
}
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
这个函数是runtime
提供的一个API
函数,获取类的实例对象所占用的内存大小
。
通过源码可知道 class_getInstanceSize
函数获取的的对象大小是8
字节对齐。
这个函数依据对象内部的属性
变化而变化;如果没有属性, 只继承了NSObject
,则类的实例对象实际占用的内存大小是8
,
malloc_size
malloc_size
是系统实际开辟的内存空间,class_getInstanceSize
是实际占用的空间。根据文章顶部
代码实例1
打印可验证。这个是由系统完成的,从上面的分析看得出, 实际占用和实际分配的大小是不一样的。
0x01 -- 结构体内存对齐
这里可以参考我之前写的文章底层必备c/c++知识结构体
struct LGStruct1 {
double d; // 0-7
char c; //8
int i;
short s; //9 10 11 12 13 14 15
};
struct LGStruct2 {
double d; //8 0-7
int i; //4 8-11
char c; //1 12
short s; //2 13 14 15
};
int main() {
NSLog(@"%lu", sizeof(struct LGStruct1)); // 24
NSLog(@"%lu", sizeof(struct LGStruct2)); // 16
return 0;
}
上面片段代码Test1
和Test2
的sizeof
是多少?
从打印可得知LGStruct1
的内存大小为24
; LGStruct2
为16
;
俩个结构体 ,数据类型一致,顺序不一样, 导致输出的结果也不一样, 这就涉及到
内存对齐
;至于为什么系统要做这件事; 上面说
sizeof
的时候也说了, 简单来说,提高性能。
内存对齐规则
【原则一】 数据成员的对齐规则可以理解为
min(m, n)
的公式, 其中m
表示当前成员的开始位置
,n
表示当前成员所需要的位数
。如果满足条件m 整除 n
(即m % n == 0
),n
从m
位置开始存储, 反之继续检查 m+1 能否整除 n
, 直到可以整除, 从而就确定了当前成员的开始位置。【原则二】数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的
自身长度
作为外部结构体的最大成员的内存大小,比如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度
为8【原则三】最后
结构体的内存大小
必须是结构体中最大成员内存大小
的整数倍,不足的需要补齐。
对齐规则验证
根据上面的实例代码, 画了一章对齐示意图
- 根据规则一 进行内部成员对齐。所占用字节
18
个; - 根据规则三 整体对齐后,占用字节
24
个。
结构体嵌套对齐
struct mystruct4 {
int a;
struct mystruct5 {
short c;
double b;
char c1;
}str5;
int d;
}s4;
printf("mystruct4内存大小 %lu\n", sizeof(s4)); // 40
printf("mystruct5内存大小 %lu\n", sizeof(s4.str5)); // 24
结构体嵌套对齐的算法规则是
-
int
从0
开始, 占4
个字节, 位置是0 1 2 3
。 -
str5
按照规则二从double
算, 空白填充4 5 6 7
;从8-24
是嵌套结构体所占大小。 - 接着
25
,不满足规则一,25 26 27
填充空白,28 29 30 31
占位。 - 一共使用了
32
个字节,按照规则三整体对齐,是40
个字节。
0x02 -- 内存优化(属性重排)
x指令
打印对象内存地址 ==memory read person
x/4gx
以16进制打4个印人可以方便看的内存
@interface Other: NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic) char sex;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic) char garde;
@end
@implementation
@end
Other *person = [Other alloc];
person.name = @"liming";
person.nickName = @"ll";
person.sex = "m";
person.garde = "A";
person.age = 23;
通过LLDB
命令查看对象
的内存布局。x
命令查看的不够直观,因为iOS
是小端模式,所以是内存值是倒着,使用x/4gx
打印出来的方便直接查看。
通过打印,把对象存储的值都直观的输出了, 而且在0x000000170000414d
这个值里存储了对象里属性三个值,这就是属性重排
,虽然对象编译到底层是结构体
,按照结构体对齐,会极大的浪费空间 ,苹果在这一层又做了优化,就是属性重排
,达到优化空间的目的。利用空间换时间
参考
结构体内存对齐