- 内存对齐是我们定义数据结构中一个必须要了解的知识点,内存对齐的目的在于能让系统从内存中快速的查找并且获取到我们想要获取的数据,达到空间换取时间的目的。下面简单介绍一下内存对齐的原理
内存对齐的规则
- 1.数据成员对齐规则,结构体或者联合体的第一个成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员的大小或者该成员的子成员的大小的整数倍开始
- 2.结构体作为成员:如果一个结构体A中有结构体B作为子成员,B中存放有char,int,double等元素,那么B应该从double也就是8的整数倍开始存储
- 结构体的总体大小,即sizeof的结果,必须是其内部最大成员的整数倍,不足的需要补齐
32位下采用4字节对齐,64位下采用8字节对齐
struct A {
int a; //4字节,0-3
char b;//1字节,4 补齐567
double c;// 8字节 8-15
int *p; // 8字节 16-23 大小24字节
}AA;
struct B {
int a; //0,1,2,3 补齐4,5,6,7
int *p; // 8,9,10,11,12,13,14,15
char b; // 16补齐17,18,19,20,21,22,23
double c;// 24,25,26,27,28,29,30,21,32
}BB;
struct C {
int a; // 0-3,
char b;// 4,补齐5-7
double c;// 8-15
int *p;// 16-23
struct B bb; //结构体最大成员为8,从24开始,24+32 = 56字节是8的整数倍ok
}CC;
// 输出
NSLog(@"A = %d - B = %d - C = %d",sizeof(AA), sizeof(BB), sizeof(CC));
A = 24 , B = 32, C = 56
对象的内存对齐
- 对象在alloc过程中,会调用_class_createInstanceFromZone进行内存申请并且创建对象
- 在calloc之前,首先调用了类的instanceSize方法进行内存空间大小的计算,而在*instanceSize内部采取了8字节对齐的方式,之后为了对象内存安全,在最后返回之前做了最小为16字节的处理,那么可以得出结论,对象的最小内存为16字节
size_t size = cls->instanceSize(extraBytes);
// 64位下 WORD_MASK为7,也就是8字节对齐
static inline uint32_t word_align(uint32_t x) {
// (x + 7) >> 3 << 3
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
- 之后进入calloc流程,进行具体的内存开辟,在使用calloc申请内存的过程中,首先调用malloc_zone_calloc方法
void *
calloc(size_t num_items, size_t size)
{
void *retval;
retval = malloc_zone_calloc(default_zone, num_items, size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}
- 在其内部调用zone->calloc初始化并且返回了一个ptr指针
ptr = zone->calloc(zone, num_items, size);
- 断点到此处,并且打印 zone->calloc ,根据提示找到default_zone_calloc
(lldb) p zone->calloc
(void *(*)(_malloc_zone_t *, size_t, size_t)) $1 = 0x0000000100381a4b (.dylib`default_zone_calloc at malloc.c:249)
(lldb)
- 在default_zone_calloc方法内部又看到熟悉的zone->calloc,继续打印得到提示nano_calloc
(lldb) p zone->calloc
(void *(*)(_malloc_zone_t *, size_t, size_t)) $2 = 0x000000010038302e (.dylib`nano_calloc at nano_malloc.c:878)
(lldb)
- 在malloc的源码中搜索nano_calloc,于nano_calloc.h文件中找到该方法,其中的核心代码_nano_malloc_check_clear,进行内存申请,并且返回一个成熟的指针ptr
static void *
nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size)
{
size_t total_bytes;
if (calloc_get_size(num_items, size, 0, &total_bytes)) {
return NULL;
}
if (total_bytes <= NANO_MAX_SIZE) {
// 核心代码,返回一个分配好的地址
void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
if (p) {
return p;
} else {
/* FALLTHROUGH to helper zone */
}
}
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->calloc(zone, 1, total_bytes);
}
- 其中_nano_malloc_check_clear方法内部的segregated_size_to_fit则是对象开启内存的大小的算法,表明其实际对齐原则为16字节对齐
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)
static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
size_t k, slot_bytes;
if (0 == size) {
size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
}
// 这里的关键代码进行16字节对齐
// (size + 1<<4 -1) >> 4
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
*pKey = k - 1; // Zero-based!
return slot_bytes;
}
小结
- 这样基本得出结论,对象内存的申请按照8字节对齐,不满16字节按照16字节计算;但是实际上calloc实际开辟内存的时候,则是进行了16字节对齐,避免对象之间发生溢出和野指针的问题