本章主要由结构体内存对齐到苹果的属性重排, 以及
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这个值里存储了对象里属性三个值,这就是属性重排,虽然对象编译到底层是结构体,按照结构体对齐,会极大的浪费空间 ,苹果在这一层又做了优化,就是属性重排,达到优化空间的目的。利用空间换时间