内存对齐应该是编译器的管辖范围。编译器为程序中的每个数据单元安排在适当的位置上。
对齐原因:
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
规则定义:
- 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
- 结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
- 结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
规则解析:
- 规则1中表明数据成员的存放是按照定义的顺序依次存放的
- #pragma pack是对齐系数,每个平台不一样,程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数(32位平台一般为4,64位平台一般为8)。iOS下默认为8。这个数值大家可以通过调试#pragma pack(n)测试验证得到。
- 规则1,当第x(x>1)个成员y存放的时候,y按照min(n,m)来对齐存放,其中n为对齐系数,m为成员y的数据类型长度。
- 在完成各个数据成员的存放排列后,通过规则2,取min(n,maxM)进行对齐,其中n为对齐系数,maxM为所有数据成员类型中长度的最大值。
基础知识:
1.一个字节包含8个二进制位
2.一个十六进制位可用4个二进制位表示
3.一个字节可以由2个十六进制位表示
- 0x0000 0000 0000 0008表示16个16进制位,可以表示8个字节
所以8可以用8个字节0x0000 0000 0000 0008表示,或者4个字节0x0000 0008,或者2个字节0x0008,取决于定义8的数据类型。 - 字符'a'换成ASCII码为97,可以用 0x61表示。
- iOS系统的编译平台是按照小端法进行编译。
实例分析:
struct Person {
char a;
long b;
int c;
short d;
}MyPerson;
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyPerson.a = 'a';
MyPerson.b = 8;
MyPerson.c = 4;
MyPerson.d = 2;
NSLog(@"Adress=======MyPerson:%p",&MyPerson);
NSLog(@"Size=======MyPerson:%lu",sizeof(MyPerson));
}
return 0;
}
第一个成员char类型的成员a='a'占用1字节,此时:
a: 0x61
第二个成员long类型的成员b=8占用8个字节,根据规则解析3,b=8按照min(8,8)=8对齐,b的起始位置为8的倍数,不满足,a需要补齐7个字节保证b的起始位置为8的倍数
此时:
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008
第三个成员int类型的成员c=4占用4个字节,根据规则解析3,整数c=4需要按照min(8,4)=4进行对齐,c的起始位置需要为4的整数倍,现在已经满足
此时:
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008
c:0x0000 0004
第四个成员short类型的整数d=2占用2个字节,根据规则解析3,d按照min(8,2)=2进行对齐,d的起始位置需要为2的整数倍,现在已经满足
此时:
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008
c:0x0000 0004
d:0x0002
根据规则解析4,结构体需要进行整体对齐,取min(n,maxDataLength) = max(8,8) = 8对齐,现在为8+8+4+2=22字节,需要补2个字节,按照排列顺序,在d占用内存段补2个字节;
最后得到
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008
c:0x0000 0004
d:0000 0002
其中我们看把c和d看成共占用一段8字节的内存,因为对齐系数为8,编译器按照8的整数倍来读取内存地址。
按照小端法进行修正,此时内存排列应该内应该是
a:0x0000 0000 0000 0061
b:0x0000 0000 0000 0008
dc:0x0000 0002 0000 0004,
其中dc:0x0000 0002 0000 0004的第1-8位表示成员d的值,右边第9-16位表示成员c的值
综上,MyPerson结构体整体占用8+8+8=24字节
结构体嵌套结构体:
struct MyPerson1{
char a;
long b;
int c;
short d;
}MyPerson1;
struct MyPerson2{
char a;
long b;
int c;
short d;
struct MyPerson1 person;
}MyPerson2;
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyPerson1.a = 'a';
MyPerson1.b = 8;
MyPerson1.c = 4;
MyPerson1.d = 2;
MyPerson2.a = 'a';
MyPerson2.b = 8;
MyPerson2.c = 4;
MyPerson2.d = 2;
MyPerson2.person = MyPerson1;
NSLog(@"Adress=======MyPerson1:%p",&MyPerson1);
NSLog(@"Adress=======MyPerson2:%p",&MyPerson2);
NSLog(@"Size=======MyPerson2.person:%lu", sizeof(MyPerson2.person));
NSLog(@"Size=======MyPerson2:%lu", sizeof(MyPerson2));
}
return 0;
}
输出如下:
person是一个结构体,最大成员为long类型,长度为8,所以按照min(8,8) 来对齐存放,已满足条件,所以直接存储即可,因此MyPerson2结构体整体占用48字节。
类的内存对齐:
定义一个类
@interface Teacher : NSObject
@property (nonatomic, assign) char a;
@property (nonatomic, assign) long b;
@property (nonatomic, assign) int c;
@property (nonatomic, assign) short d;
@end
并在main.m中添加如下代码:
Teacher *t = [[Teacher alloc] init];
t.a = 'a';
t.b = 8;
t.c = 4;
t.d = 2;
NSLog(@"Adress=======t:%p",t);
输出如下:
可以看到,该对象的第一个八字节存储着isa的值,而对象的第2个八字节和第3个八字节这2个内存段存储了我们定义的成员a、b、c、d(准确表述为_a,_b,_c,_d)的值,说明编译器做了相应的优化,不会直接按照我们在类中定义成员的顺序生成构造对应的结构体。
class_getInstanceSize和malloc_size
class_getInstanceSize:依赖于
malloc_size:依赖于
新建一个类XYPerson继承于NSObject:
@interface XYPerson : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) long height;
@property (nonatomic, assign) char c1;
@property (nonatomic, assign) float m_float;
@end
```
在main函数中写上如下代码:
```
#import "XYPerson.h"
#import
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
XYPerson *p = [[XYPerson alloc] init];
p.name = @"Ring"; // NSString 8
p.age = 18; // int 4
p.height = 188; // long 8
p.c1 = 'a'; // char 1
p.m_float = 12.6; // float 4
NSLog(@"申请内存大小为:%lu——-系统开辟内存大小为:%lu", class_getInstanceSize([p class]), malloc_size((__bridge const void *)(p)));
}
return 0;
}
输出如下:
iOS中,对象的属性是8字节对齐,而对象是16字节对齐。
因为内存是连续的,通过 16 字节对齐规避风险和容错,可以防止访问溢出。同时,也提高了寻址访问效率,也就是空间换时间。
·