知识点须知
C和OC各数据类型在32位和64位CPU中的占用的字节大小列表:
C | OC | 32位 | 64位 |
---|---|---|---|
bool | BOOL(64位) | 1 | 1 |
signed char | (_ _signed char)int8_t、BOOL(32位) | 1 | 1 |
unsigned char | Boolean | 1 | 1 |
short | int16_t | 2 | 2 |
unsigned short | unichar | 2 | 2 |
int int32_t | NSInterger(32位)、boolean_t(32位) | 4 | 4 |
unsigned int | boolean_t(64位)、NSUInteger(32位) | 4 | 4 |
long | NSInteger(64位) | 4 | 8 |
unsigned long | NSUInteger(64位) | 4 | 8 |
long long | int64_t | 8 | 8 |
float | CGFloat(32位) | 4 | 4 |
double | CGFloat(64位) | 8 | 8 |
对象的内存大小与属性重排
-
class_getInstanceSize
首先我们来看一下Runtime
函数class_getInstanceSize
获取实例的大小。
其底层源码是:
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
类的属性大小,采用8字节对齐原则
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
# define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
发现取决于data()->ro->instanceSize
,即是对象bits
在运行时的class_rw_t
的ro
,只读内存的实例大小instanceSize
,其实在编译期就定了,在运行时,class_rw_t
的存在(因为其实可读可写的)就决定可以动态的添加方法协议,动态关联属性。
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
小结:
从源码可以知道,对象的大小与属性有关
- 验证对象大小与属性相关
新建一个类,没有任何属性,看看创建出来的对象大小是多少
@interface PSYPerson : NSObject
///** */
//@property (nonatomic,copy) NSString *name;
///** */
//@property (nonatomic,assign) NSInteger age;
///** */
//@property (nonatomic,copy) NSString *nickName;
@end
....
PSYPerson *psy1 = [[PSYPerson alloc] init];
PSYPerson *psy2 = [[PSYPerson alloc] init];
NSLog(@"psy1对象大小:%lu",class_getInstanceSize(psy1.class));
NSLog(@"psy2对象大小:%lu",class_getInstanceSize(psy2.class));
输出结果:
从结果可以看到,为什么是8呢,明明没有任何属性?这里首先说明,对象默认继承自obj_object
结构体,默认继承了一个isa
指针占8个字节。
给类添加属性@property (nonatomic,copy) NSString *name;
,重新运行程序,查看打印结果:
与我们预想的一样,为了进一步验证,添加如下属性(跑在6s真机上测试,根据上面知识点须知 C和OC不同数据类型在32位和64位CPU中占有字节大小列表
):
@interface PSYPerson : NSObject
/** 8字节 */
@property (nonatomic,copy) NSString *name;
/** 4字节 */
@property (nonatomic,assign) int age;
/** 1字节 */
@property (nonatomic) char msg;
/** 1字节 */
@property (nonatomic) BOOL isWorking;
@end
分析:
8字节+4字节+1字节+1字节 = 14字节
再加上isa指针8字节 :14字节+8字节 = 22字节,根据8字节对齐原则应该是24字节
源码说只与类的ivars有关,那方法对其是否有影响呢?笔者有添加了一个类方法一个对象方法,继续输出印证,发现还是24字节;
方法对对象大小无影响
- 属性重排
对象的属性在内存中是怎样存在的呢?
因为属性是有32字节,笔者特意打印了8段数据,实际对象的地址是从0x2807e05c0
开始,占32个字节,也就是下图中红框起来的数据区,可以知道,在对象被创建出来的时候属性被默认赋值为 0
或则 nil
,其他的是脏数据。
赋值之后,重新打印查看内存数据:
image.png
可发现不管属性顺序是怎样,先赋值哪一个,运行的时候,苹果会对对象的属性进行重排,使其内存优化。
总结
- 对象属性内存采用
8字节对齐
- 对象
大小与ivars有关
,与方法无关 - 苹果会进行
属性重排
,以优化内存
结构体内存对齐
上面研究了对象内存对齐,对于结构体是什么样的呢?研究结构体其实是有很大的意义,因为在底层探索过程中,基本上90%以上都是结构体共用体。
struct PSYStruct1 {
double a; // 8
char b; // 1
int c; // 4
short d; // 2
}struct1;
struct PSYStruct2 {
double a; // 8
int b; // 4
char c; // 1
short d; // 2
}struct2;
估计很大一部分人认为,这两个结构体一抹抹一样样,大小都是16字节,可是运行结果却让人大跌眼镜:
image.png结构体内存对齐
-
数据成员对齐规则:
结构体(struct
)或联合体/共用体(union
)的数据成员,第一个数据成员放在offset
为0️⃣的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体)的整数倍开始-
eg:
比如int为4字节,则要从4的整数倍地址开始存储
-
-
结构体作为成员:
如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。-
eg:
struct a
里存有struct b
,b
里有char
,int
,double
等元素,那b
应该从8的整数倍开始存储)
-
-
规则三:
结构体的总大小,也就是sizeof
的结果,必须是其内部最大成员的整数倍,不足的要补齐。
根据数据成员对齐规则:
double a;
// 8 从0开始存8位 也就是 [0 , 7]
char b;
// 1 [8]
int c;
// 4 (9, 10, 11, [12, 13, 14, 15] 从4的整数倍开始存,也就是12开始
short d;
// 2 [16, 17] 17个字节,但是根据规则三,大小为最大成员8的整数倍取24字节
同理
double a;
// 8 从0开始存8位 也就是 [0 , 7]
int b;
// 4 [8, 9, 10, 11] 因为8是4的整数倍,因此从8开始存
char c;
// 1 [12]
short d;
// 2 [14, 15] 根据规则三,大小为最大成员8的整数倍所以总共16个字节
结构体嵌套情况
struct PSYStruct3 {
double a;
int b;
char c;
short d;
int e;
struct PSYStruct1 str;
}struct3;
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 PSYStruct1 str;
// (20, 21, 22, 23, [24, 47]
根据规则结构体作为成员:
,str
结构体开始存储的位置是str
结构体内部最大元素的大小8
的整数倍24
开始,占24个字节,就是到47
所以整个结构体占48字节。
image.png
网友评论