获取内存大小相关的三个函数
sizeof:
sizeof是C/C++中的一个操作符(operator),简单的说其作用就是返回一个对象或者类型所占的内存字节数。sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用;最新的C99标准规定sizeof也可以在运行时刻进行计算,但在没有完全实现C99标准的编译器中就行不通了;所以我们最好还是认为sizeof是在编译期执行的,这样不会带来错误,让程序的可移植性强些;
class_getInstanceSize:
获取实例对象中成员变量的内存大小;
malloc_size:是为其实际分配了多少空间;
通过下面的代码看一下:
#import <objc/runtime.h> #include <malloc/malloc.h> struct NSObject_IMPL { Class isa; }; struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; }; @interface Person : NSObject @end @implementation Person @end int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [[Person alloc] init]; NSLog(@"%lu", sizeof(struct Person_IMPL)); NSLog(@"%ld", class_getInstanceSize([p class])); NSLog(@"%lu", malloc_size((__bridge const void *)(p))); } return 0; }
打印结果
8 8 16
sizeof:计算的就是某个类型所占用的空间大小,也就是结构体Person_IMPL所占用的内存大小;如果直接sizeof(p)则为p这个指针所占用的大小,无论Person里面有多少个成员变量,sizeof(p)的大小都是8个字节;
class_getInstanceSize:通过源码可知调用顺序如下:
size_t class_getInstanceSize(Class cls) { if (!cls) return 0; return cls->alignedInstanceSize(); }
接下来调用cls->alignedInstanceSize:
uint32_t alignedInstanceSize() const { return word_align(unalignedInstanceSize()); }
uint32_t unalignedInstanceSize() const { ASSERT(isRealized()); return data()->ro()->instanceSize; }
接下来调用word_align内联函数:
#ifdef __LP64__ # define WORD_MASK 7UL #else # define WORD_MASK 3UL #endif static inline uint32_t word_align(uint32_t x) { return (x + WORD_MASK) & ~WORD_MASK; }
由word_align函数可知,计算的最终结果始终是8的倍数(8,16,24...);malloc_size:为实际分配的大小,始终是16字节对齐,16的倍数,具体可参考libmalloc源码;内存对齐
基本概念
- 数据类型自身的对齐值:对于char型数据,其自身对齐值为1,对于short型为2,对于int, float类型,其自身对齐值为4,对于double型,其自身对齐值为8,单位字节,具体也可参考类型表;
- 结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值;
- 指定对齐值:#pragma pack (value)时的指定对齐值value,OC上对齐值为8;
数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
对齐规则
- 数据成员的对齐规则可以理解为min(m, n)的公式, 其中m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件m整除n(即 m % n == 0), n从m位置开始存储, 反之继续检查m+1能否整除n, 直到可以整除, 从而就确定了当前成员的开始位置。
- 数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8;
- 最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的需要补齐。
各种类型内存占用对照表:分析下面这两个结构体所占用的内存大小
struct Struct1 { char a; //1 double b; //8 int c; //4 short d; //2 };//24 struct Struct2 { double b; //8 int c; //4 short d; //2 char a; //1 };//16 int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"Struct1 = %lu - Struct2 = %lu",sizeof(struct Struct1), sizeof(struct Struct2)); } return 0; }
打印结果:
Struct1 = 24 - Struct2 = 16
从定义的变量和类型来看两者是一致的,只是顺序不一样,为什么占用的内存大小不一致呢?根据内存对齐规则来进行分析:
Struct1:
char a:从0开始,占1个字节,此时min(0, 1),0除以1可以整除,故0存储a;
double b:从1开始,占8个字节,此时min(1, 8),1除以8不可以整除,直到min(8, 8)才可以,故b的内存空间8-15,来存储b;
int c:从16开始,占4个字节,此时min(16, 4);可以整除,故16-19存储c;
short d:从20开始,占2个字节,此时min(20, 2),可以整除,故20-21存储d;
又因为Struct1最大的变量占用8个字节,因需8字节对齐,21距离最近的8的倍数的值为24,所以Struct1占用了24字节的大小;
Struct2:
double b:从0开始,占8个字节,此时min(0, 8),可以整除,故0-7存储b;
int c:从8开始,占4个字节,此时min(8, 4);可以整除,故8-11存储c;
short d:从12开始,占2个字节,此时min(12, 2),可以整除,故12-13存储d;
char a:从14开始,占1个字节,此时min(14, 1),可以整除,故14存储a;
又因为Struct2最大的变量占用8个字节,因需8字节对齐,14距离最近的8的倍数的值为16,所以Struct2占用了16字节的大小;
探索OC类定义成员变量和属性顺序不同导致占用分配大小的不同?
OC类定义成员变量:
@interface Person : NSObject { @public char a; //1 double b; //8 int c; //4 short d; //2 } @end @implementation Person @end @interface Person2 : NSObject { @public double b; //8 int c; //4 short d; //2 char a; //1 } @end @implementation Person2 @end int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"%ld, %ld", class_getInstanceSize([Person class]), class_getInstanceSize([Person2 class])); } return 0; }
运行结果:
32, 24
相比于Struct1和Struct2多了一个isa指针,这里就不再描述计算细节了,我们会发现只是顺序不同,定义成员变量的时候,占用的内存也不相同;
OC类定义属性:
@interface Person : NSObject @property(assign, nonatomic) char a; @property(assign, nonatomic) double b; @property(assign, nonatomic) int c; @property(assign, nonatomic) short d; @end @implementation Person @end @interface Person2 : NSObject @property(assign, nonatomic) double b; @property(assign, nonatomic) int c; @property(assign, nonatomic) short d; @property(assign, nonatomic) char a; @end @implementation Person2 @end int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"%ld, %ld", class_getInstanceSize([Person class]), class_getInstanceSize([Person2 class])); } return 0; }
运行结果:
24, 24
从运行结果可以看出,当定义属性的时候,两者虽然顺序不同,占用的内存空间却是相同的。
我们查看一下底层实现:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; char _a; short _d; int _c; double _b; };
struct Person2_IMPL { struct NSObject_IMPL NSObject_IVARS; char _a; short _d; int _c; double _b; };
我们会发现,当转换成c++实现的时候,顺序自动又调整好了,故当我们再获取的时候,两者的值是相同的(当定义成员变量的时候、内部未自动调整顺序,自行尝试),苹果内部对内存做了优化,对属性进行了重排;总结
- sizeof:某个类型所占用的内存大小;
- class_getInstanceSize:采用8字节对齐的方式排列,分配的大小为8的整数倍,为OC实例对象至少需要的内存空间;
- malloc_size:采用16字节对齐方式排列,实际分配内存的大小为16的整数倍,为分配给OC对象的实际内存大小;
- 对于OC类,成员变量的定义顺序影响内存分配的大小,而属性的定义顺序不会影响内存的分配,通常我们可以把占用字节数较高的类型放在后面;