内存对齐

202 阅读3分钟

对象实例

在上一节alloc的源码分析中看到,成员变量是以8字节内存对齐的。我们来验证一下 先声明一个Person的类

@interface Person : NSObject

@end

打印如下数据:

 Person *person = [Person alloc];
       
NSLog(@"%@ - %lu - %lu - %lu",person, sizeof(person),class_getInstanceSize([person class]),malloc_size((__bridge const void *)(person)));

打印的结果如下: 截屏2021-06-14 下午4.06.21.png sizeof(person),我们来看一下person,person是一个Person *类型的。Person继承与NSObject

  • sizeOf:sizeof计算数据(数组,变量,类型,结构体等)所占内存空间,单位字节
  • class_getInstanceSize:计算对象及成员变量占用的内存空间,需要注意父类属性isa8字节对齐
  • malloc_size:malloc_size计算实际向系统申请开辟的内存空间,16字节对齐
@interface NSObject <NSObject> {
     ....
    Class isa  OBJC_ISA_AVAILABILITY; //有一个isa
    ....
}

//class是一个结构体指针的类型
typedef struct objc_class *Class;

所以上面的sizeof(person)就是结构体指针的大小,故为8. 我们来看class_getInstanceSize([LGPerson class])为8,

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

我们看一下cls->alignedInstanceSize()

// Class's ivar size rounded up to a pointer-size boundary. 
uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
 // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }
    
    static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
    //x + 7 & ~7 这是一个以8对齐的操作
}
    

由于person中没有成员变量和属性,只有一个isa所以还是8. 那我们加入两个属性

@interface Person : NSObject
  @property (nonatomic, copy) NSString *name; // 8
  @property (nonatomic, assign) NSInteger age; //4
@end

截屏2021-06-14 下午4.13.12.png 8 + 4 + 8(isa) = 20, 8字节对齐,所以是24。 分别添加方法、成员变量。看看是否影响对象实例的大小


@interface Person : NSObject {
    NSString *hobby;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

+(void)test;
-(void)test;

@end

截屏2021-06-14 下午4.18.39.png 实验证明,成员变量、属性可以影响对象实例的大小,方法是不影响的。 属性是由成员变量、存取方法组合而成,故只有成员变量影响对象实例的大小。

对象的内存以16字节对齐

malloc_size((__bridge const void *)(person)) 对象分配的内存是以16字节为单位对齐的,maclloc源码分析函数调用流程图如下:

截屏2021-06-14 下午5.31.25.png

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字节对齐
        //k = (size + 15) >> 4
	k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
        //slot_bytes = k << 4
	slot_bytes = k << SHIFT_NANO_QUANTUM;							// multiply by power of two quanta size
	*pKey = k - 1;													// Zero-based!

	return slot_bytes;
}

注:

截屏2021-06-14 下午5.35.24.png 在源码跟踪过程中ptr = zone->calloc(zone, num_items, size) 点开zone->calloc

    //是一个函数定义
    void 	*(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */

此时可以用汇编,或者打印po/p zone->calloc 打印出复制的函数为default_zone_calloc

截屏2021-06-14 下午5.37.38.png 同理,default_zone_calloc中也可打印出所指向函数

po zone->calloc
(.dylib`nano_calloc at nano_malloc.c:878)

结构体对齐原则

数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第 一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要 从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组, 结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存 储。 min(当前开始的位置mn)m=9 n=4 9 10 11 12

结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从 其内部最大元素大小的整数倍地址开始存储. (struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.) 必须是其内部最大成员的整数倍,不足的要补⻬。

收尾工作:结构体的总大小,也就是sizeof的结果,


struct LGStruct1 {
    double a;       // 8    [0 7]
    char b;         // 1    [8]
    int c;          // 4    (9 10 11 [12 13 14 15]
    short d;        // 2    [16 17] 24
}struct1;

struct LGStruct2 {
    double a;       // 8    [0 7]
    int b;          // 4    [8 9 10 11]
    char c;         // 1    [12]
    short d;        // 2    (13 [14 15] 16
}struct2;

 NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
 //  24, 16