结构体内存对齐的三大原则
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始存储。
2、结构体作为成员:如果一个结构里有结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。
3、收尾工作:结构体的总大小,必须是其内部最大成员的整数倍,不足的要补齐。
结合例子做具体分析:
struct ELStruct1{
double a; //8 [0,7]
char b; //1 [8]
int c; //4 9...11 [12,15]
short d; //2 [16,17] //取最大内部成员8的整数倍8*3=24对齐
}struct1;
struct ELStruct2{
double a; //8 [0,7]
int b; //4 [8,11]
char c; //1 [12]
short d; //2 13 [14,15] //取最大内部成员8的整数倍8*2=16对齐
}struct2;
打印结果:
struct1--24
struct2--16
我们再来一个稍微复杂一点的分析
struct ELStruct4{//接上继续分析
double a; //8 20...23 [24,31]
char b; //1 [32]
int c; //4 33...35 [36,39]
short d; //2 [40,41] //取最大内部成员8的整数倍8*6=48对齐
}struct4;
struct ELStruct1{
double a; //8 [0,7]
char b; //1 [8]
int c; //4 9...11 [12,15]
short d; //2 [16,17] //取最大内部成员8的整数倍8*3=24对齐
}struct1;
struct ELStruct2{
double a; //8 [0,7]
int b; //4 [8,11]
char c; //1 [12]
short d; //2 13 [14,15] //取最大内部成员8的整数倍8*2=16对齐
}struct2;
struct ELStruct3{
double a; //8 [0,7]
int b; //4 [8,11]
char c; //1 [12]
short d; //2 13 [14,15]
int e; //4 [16,19]
struct ELStruct4 str;
}struct3;
打印结果:
struct1--24
struct2--16
struct3--48
我们继续探究ELPerson
@interface ELPerson : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,copy)NSString *nickName;
@property(nonatomic,assign)long age;
@property(nonatomic,assign)float height;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//NSLog(@"Hello, World!");
ELPerson *per = [ELPerson alloc];
per.name = @"Eli";
per.nickName = @"Eli";
per.age = 18;
per.height = 180.6;
NSLog(@"%@ - %lu - %lu - %lu",per,sizeof(per),class_getInstanceSize([ELPerson class]),malloc_size((__bridge const void*)(per)));
}
return 0;
}
打印结果如下:
2021-06-09 15:31:08.107634+0800 KCObjcBuild[70838:2643794]
<ELPerson: 0x10068a320> - 8 - 40 - 48
sizeof()探究
我们先看看为什么sizeof(per)
打印8
per是一个对象,对象的本质是一个指针地址,指针地址的大小就是8字节
class_getInstanceSize()探究
继续分析class_getInstanceSize([ELPerson class])
ELPerson 有4个属性分别是NSString,NSString,long,float
分别是8+8+8+4=28字节,再加上isa指针8字节就是36字节
我们跟踪一下看看class_getInstanceSize()
内部实现,看看为什么打印出来40和我们分析的不一样
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
继续跟踪
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
8字节对齐
# define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
看到这里我们就明白了,他在内部进行了8字节的内存对齐算法,所以把36字节对齐为40字节返回给我们了。我们以word_align(3)
为例,下面具体分析下算法
3 <-> 0000 0011 7 <-> 0000 0111
3+7 <-> 0000 0011 ~7 <-> 1111 1000
+ 0000 1111 & 0000 1010
10 <-> 0000 1010 8 <-> 0000 1000
结果就是 word_align(3)=8
跟常见的算法 (3 + 7) >> 3 << 3
是一个效果,都是8字节对齐,要想16字节对齐就是(3 + 15) >> 4 << 4
malloc_size()打印原因探究
我们直接进入源码,绘制出流程图如下所示
calloc我们把segregated_size_to_fit()
展开分析一下
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
#define SHIFT_NANO_QUANTUM 4
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
}
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;
}
主要代码翻译一下:
( size + 16 - 1 )>> 4 << 4
可以看到这个里面也是进行了一个内存对齐的操作, 并且是16位内存对齐,和我们上面说的方法一模一样的。对40进行16位对齐,结果就是48了
总结:
对象内部的成员变量,进行8字节内存对齐。对象本身是16字节对齐。
网友评论