1. 对象内存对齐
在我们进行alloc
一个对象的时候,通过源码探究可以得出。一个对象进行内存的开辟,是以16字节
进行对齐的。但是,对象真正需要的内存,却是以8字节
对齐的。只是系统为了内存安全考虑,采取16字节
对齐的方式开辟内存空间。
ZCPerson.h
@interface ZCPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickname;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@end
main.m
int main(int argc, char * argv[]) {
@autoreleasepool {
ZCPerson *p = [[ZCPerson alloc]init];
p.name = @"zc";
p.age = 18;
p.height = 185.0;
NSLog(@"%@---%lu---%lu---%lu",p,sizeof(p),class_getInstanceSize([ZCPerson class]),malloc_size((__bridge const void *)(p)));
}
return 0;
}
输出:
2020-11-24 10:36:39.442212+0800 alloc[2456:59130] ---8---40---48
通过打印,我们可以看到第一个是输出ZCPerson
对象,第二个打印的是p
指针,指针在内存中占据8个字节。
第三个打印是ZCPerson
所需内存,我们看到输出结果是40
,如果是按照alloc
源码分析,以16字节对齐的话,应该和最后输出的一样是48
,所以从输出结果看,ZCPerson
所需内存是不可能以16自己对齐的。
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
# define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
通过观察class_getInstanceSize
方法源码发现,ZCPerson
实际所需要的内存空间是以8字节
对齐进行开辟的。
- 系统内存优化
ZCPerson.h
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickname;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
通过
x
命令可以打出地址内存情况,通过
x/4gx
可以将内存情况以16进制打印出4组数据。
输出ZCPerson
:
(lldb) po p
打印ZCPerson
地址内存情况:
x/4gx 0x600003f11260
0x600003f11260: 0x000000010b644858 0x0000001200006261
0x600003f11270: 0x000000010b642068 0x000000010b642088
(lldb) po 0x000000010b644858
ZCPerson
(lldb) po 0x000000010b642068
zc
(lldb) po 0x000000010b642088
handsomeboy
从以上输出可以看到,这里左边打印出的是地址,右边对应的是地址里存放的是属性的值。但是,当我们打印第一行第二列的值的时候,发现打出来的是乱码:
(lldb) po 0x0000001200006261
77309436513
这是因为,ZCPerson
内存被优化的结果。在上述中我们可以看到,给ZCPerson
属性赋值的还有age,c1,c2
,而这些值,正好被存储在0x0000001200006261
这里。
验证如下:
(lldb) po 0x00000012
18
(lldb) po 0x00000000000061
97
(lldb) po 0x00000000000062
98
通过打印我们可以看到打印出了18
,而打印出的97,98
也正好对应了ASCII码中的a,b
,从而,也说明了ZCPerson
在开辟内存的时候,进行了内存的重排,这样做是为了避免内存的浪费,起到优化内存的作用。
2. 结构体内存对齐
在iOS中,类在底层会被编译成结构体,那么结构体是否是和类一样也是按照16字节对齐的呢?
我们定义2个结构体如下,然后打印出他们的内存大小:
struct ZCStruct1 {
double a;
char b;
int c;
short d;
}struct1;
struct ZCStruct2 {
double a;
int b;
char c;
short d;
}struct2;
NSLog(@"%lu -- %lu",sizeof(struct1),sizeof(struct2));
按照猜想,在struct1
中,a
的字节大小为8
,b
的字节大小为1
,c
的字节大小为4
,d
的字节大小为2
,加起来等于15
,按照16字节对齐原则,打印出来的的struct1
的内存大小应该为16
,同理,struct2
打印出来的大小也应该为16
。通过输出,我们发现打印结果与我们的猜想,完全不一致,哈哈,猜想错误。
打印结果:
2020-11-24 15:16:52.961015+0800 alloc[6174:186368] 24 -- 16
在结构体中进行内存对齐时,要遵循以下原则:
-
- 数据成员对齐规则:结构体或者联合体的数据成员,第一个数据成员放在offset为0的地方,接下来的数据成员存储的位置要从该成员的大小或者成员的子成员大小(比如数组、结构体等)的整数倍开始。
-
- 结构体作为成员:如果一个结构体内有其他结构体成员,则结构体要从其内部最大元素大小的整数倍地址开始存储。(假设struct a 里有struct b,b内部含有char,int,double,那么b要从8开始存)。
-
- 结构体的总大小,必须是其内部最大成员的整数倍,不足的要补齐。
按照以上原则,我们重新来计算struct1
的大小:
- double a:
double
修饰的字节大小为8
,因为在struct1
中没有其他struct类型的数据,所以我们从0
开始存8
个字节,所占内存空间应该是[0~7]
; - char b:
char
修饰的字节大小为1
,按照顺序,从第8
位开始存,因为8
正好是1的倍数,所以b
所占空间为[8]
,大小是1
个字节; - int c:
int
修饰的字节大小为4
,按照顺序,是从第9
位开始存储,但是9
不是4
的整数倍,所以需要往后延到12
,从12
开始存储4
个字节。所以c
所占空间应该是[12,13,14,15]
; - short d:
short
修饰的字节大小为2
,按照顺便从第16
位开始存储,因为16
正好是2
的整数倍,所以可以从16
位开始存。所以,d
所占空间应该是[16,17]。
- 收尾:通过计算,我们得到
d
所占空间的最后一个空间是17
,在struct1
中,double a
占8
个字节,是最大成员,但是17
不是8
的整数倍,因此要补齐到24
。
同理,我们可以得到struct2
的大小为16。