程序员对内存对齐这个名词应该都不陌生,但是为什么要内存对齐?内存对齐的规则是怎么样的呢?
1.0 什么是内存对齐
- 定义:现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,
这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐
。 - 作用:内存是以字节为基本单位,cpu在存取数据时,是以块为单位存取,并不是以字节为单位存取。频繁存取未对齐的数据,会极大降低cpu的性能。字节对齐后,会减低cpu的存取次数,这种以空间换时间的做法目的降低cpu的开销。cpu存取是以块为单位,存取未对齐的数据可能开始在上一个内存块,结束在另一个内存块。这样中间可能要经过复杂运算在合并在一起,降低了效率。
字节对齐后,提高了cpu的访问速率
2.0 内存对齐的规则
首先我们需要了解一下各种数据类型所占的内存大小,如下表所示:
然后我们先写个示例demo:
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;
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
}struct2;
输出结果如下:
struct1内存:24-struct2内存:16
为什么具有相同数据成员的两个结构体,他们所占的内存大小不一样呢?仔细看这两个结构体只有数据成员顺序不一样,难道跟数据成员的顺序有关?
先看下内存对齐的规则:
数据成员对⻬规则
:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方(即首地址的位置),以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。结构体作为成员
:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char、int 、double等元素,那b应该从8的整数倍开始存储.)结构体的总大小,也就是sizeof
的结果,必须是其内部最大成员的整数倍,不足的要补⻬。
对照这个规则就能看懂上面demo了,以struc1为例:double的大小为8个字节, a成员变量将会占据前首地址开始的8个字节,即[0,7];char的大小为1个字节,b成员变量从位置8开始,8是1的整数倍,即[8] ;int的大小为4个字节,c成员变量从位置9开始,但9不是4的整数倍,往后顺延到是4的整数倍位置12开始,即[12,13,14,15];short的大小为2个字节,d成员变量从位置16开始,16是2的整数倍,即[16,17]。由于结构体的总大小是最大成员a的整数倍,a占用8个字节,所以struct1的大小是24。注意:起始位置从0开始
根据这个规则我们再来看一下结构体作为成员变量又是什么样子的?
还是先写个demo跑一下:
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 LGStruct3 {
double a; //8 [0 7]
int b; //4 [8,9,10,11]
char c; //1 [12]
short d; //2 (13 [14 15]
int e; //4 [16 17 18 19]
struct LGStruct1 str; //24 (20 21 22 23 [24....48] 总大小:48
}struct3;
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
NSLog(@"struct1内存:%lu-struct3内存:%lu",sizeof(struct1),sizeof(struct3));
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
输出如下:
struct1内存:24-struct3内存:48
分析:LGStruct1如上面的分析一样就不再阐述,占用24个字节。LGStruct3中,数据成员a、b、c、d、e也如上不再阐述,即[0,19],重点看一下数据成员str结构体,根据规则结构体成员要从其内部最大元素大小的整数倍地址开始存储,LGStruct1内部最大元素是数据成员a,占8个字节,那么str的起始位置必须是8的整数倍,即从位置24开始占中24个字节,结构体总大小必须是其内部最大成员的整数倍,即必须是24的整数倍,所以总大小为48。
3.0 对象的内存对齐
上面我们一直分析的是结构体,那么对象的内存分配是不是也是一样的呢?对象的本质就是结构体。
我们先上demo:
@interface GYPerson : NSObject
// isa->8
@property (nonatomic, copy) NSString *name; //8
@property (nonatomic, copy) NSString *nickName;//8
@property (nonatomic, assign) int age; //4
@property (nonatomic, assign) long height; //8 总大小:40
@end
@implementation GYPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
GYPerson *person = [GYPerson alloc];
person.name = @"隔壁老王";
person.nickName = @"lw";
NSLog(@"%@ - 指针内存:%lu - GYPerson对象所需内存:%lu -GYPerson实际内存: %lu",person,sizeof(person),class_getInstanceSize([LGPerson class]),malloc_size((__bridge const void *)(person)));
}
return 0;
}
输出如下:
<GYPerson: 0x10053a600> - 指针内存:8 - GYPerson对象所需内存:40 -GYPerson实际内存: 48
分析:person是指针,指针占用8个字节。GYPerson对象结构体内部根据结构体内存分配原则,必须是最大数据成员8的整数倍,即为40。那么为什么最后的实际内存分配是48呢?
简单粗暴一点断点调试分析源码libmalloc源码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
void *p = calloc(1, 40);
NSLog(@"%lu",malloc_size(p));
}
return 0;
}
调试源码发现按顺序调用了如下几个函数:
calloc->_malloc_zone_calloc->default_zone_calloc->_nano_malloc_check_clear->segregated_size_to_fit
关键函数segregated_size_to_fit如下:
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
}
//NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
//左边4位 右移4位 即16的整数倍
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;
}
源码里规定了对象的内存分配按16个字节对齐
,所以GYPerson对象实际分配了48个内存空间而不是40。
总结:
- 结构体内部成员变量按8字节对齐
- 对象的内存分配按16字节对齐