0x00序
本着加深对Objective-C 对象模型的理解和记忆目的,于是有了下文的简单实践操作。
0x01 疑问
在以下代码中,你能描述清楚以下问题吗?
TestClass
的实例对象tcA
和tcB
的内存结构是怎么样的TestClass
的实例对象的大小
@interface TestClass : NSObject
{
@public
int myInt;
NSString *name;
}
@property (nonatomic, assign) NSInteger age; ///< 年龄
@end
@implementation TestClass
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
//创建实例tcA
TestClass *tcA = [[TestClass alloc] init];
tcA->myInt = 100;
tcA.age = 7;
tcA->name = @"a";
//创建实例tcB
TestClass *tcB = [[TestClass alloc] init];;
tcB->myInt = 101;
tcB.age = 5;
tcB->name = @"A";
}
有什么办法可以直观看到实例对象tcA
或tcB
的内存结构吗?
0x02 直观了解实例对象内存结构
通过NSData
的+ (instancetype)dataWithBytes:(nullable const void *)bytes length:(NSUInteger)length;
方法,可以我们直观看到对象的内存结构!
那么,修改main函数如下:
int main(int argc, char * argv[]) {
//创建实例tcA
TestClass *tcA = [[TestClass alloc] init];
tcA->myInt = 100;
tcA.age = 7;
tcA->name = @"libai";
//创建实例tcB
TestClass *tcB = [[TestClass alloc] init];;
tcB->myInt = 101;
tcB.age = 5;
tcB->name = @"gongben";
//新增代码,将对象转换为字节流Data
long tcSize = class_getInstanceSize([TestClass class]);
NSData *tcAData = [NSData dataWithBytes:(__bridge const void *)tcA length:tcSize];
NSData *tcBData = [NSData dataWithBytes:(__bridge const void *)tcB length:tcSize];
NSLog(@"TestClass object tcA contans %@", tcAData);
NSLog(@"TestClass object tcB contans %@", tcBData);
NSLog(@"tcSize size= %zu", tcSize);
NSLog(@"TestCalss memory address = %p", [TestClass class]);
}
输出日志如下:
2018-05-10 14:54:27.068685+0800 TestApp[79148:1558980] TestClass object tcA contans <58f25f05 01000000 64000000 00000000 f8e15f05 01000000 07000000 00000000>
2018-05-10 14:54:27.069407+0800 TestApp[79148:1558980] TestClass object tcB contans <58f25f05 01000000 65000000 00000000 18e25f05 01000000 05000000 00000000>
2018-05-10 14:54:27.069653+0800 TestApp[79148:1558980] tcSize size= 32
2018-05-10 14:54:27.069795+0800 TestApp[79148:1558980] TestCalss memory address = 0x1055ff258
0x03 字节流分析
那么下面这两字节流分别怎么理解?
TestClass object tcA contans <58f25f05 01000000 64000000 00000000 f8e15f05 01000000 07000000 00000000>
TestClass object tcB contans <58f25f05 01000000 65000000 00000000 18e25f05 01000000 05000000 00000000>
首先我们知道TestClass
继承于NSObject
, 而NSObject
的结构如下,它只有一个成员或者叫实例变量
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
那么结合TestClass
被本身也具备的个实例变量:myInt
、name
、age
,那么我们可以猜测TestClass
的实例对象的内存结构如下:
第一个8字节
58f25f05 01000000
根据Little Endian 序(注2)规则”低地址存放低位,高地址存放高位“,那么该字节还原成十六进制值是
00000001055ff258
即0x1055ff258
。与上述第4条日志打印的TestCalss
的地址相等。第二个8字节
64000000 00000000
同上,那么该字节还原成十六进制值是
000000 0000000064
,十进制为100
。与tcA->myInt
的值相等第三个8字节
f8e15f05 01000000
同上,那么该字节还原成十六进制值是
000000010055fe1f8
,在gbd调试环境下,通过输出该地址的值,与tcA->name
的值相等(lldb) po (NSString *)0x0000000100537218 libai (lldb)
第四个8字节
07000000 00000000
同上,那么该字节还原成十六进制值是
000000 0000000007
,十进制为7
。与tcA.age
的值相等
由此可见,实例对象的内存结构正如上图那样分布!
0x04 实例对象大小
从第三条日志得到,tcA或者tcB的内存大小都是32字节
:
2018-05-10 14:54:27.069653+0800 TestApp[79148:1558980] tcSize size= 32
通过计算各个成员变量的值,得到的结果是28字节
:
sizeof(tcA->myInt) + sizeof(tcA->name) + sizeof(tcA.age) + sizeof(tcA->isa) = 4 + 8 + 8 + 8 = 28
也就是说,sizeof(对象)的大小,并不一定等于各个数据成员的大小之和!这涉及到”内存字节对齐”
参考:内存字节对齐
struct x_ {
char a; // 1 byte
int b; // 4 bytes
short c; // 2 bytes
char d; // 1 byte
} MyStruct1;
struct y_ {
int b; // 4 bytes
char a; // 1 byte
char d; // 1 byte
short c; // 2 bytes
} MyStruct2;
NSLog(@"%lu,%lu", sizeof(MyStruct1), sizeof(MyStruct2));
上述代码打印出来的结果为:12,8
注
如何确定指针大小
int numb = 12; int *p = &numb; NSLog(@"指针p大小:%zu bytes", sizeof(p));//指针p大小:8 bytes
如何确定字节序
什么是大端与小端(Little Endian 与 Big Endian)
- Big Endian 是指低地址端 存放 高位字节。
- Little Endian 是指低地址端 存放 低位字节。
那么,直接使用代码通过输出直接来验证是大端还是小端
int a = 0x12345678; if( *((char*)&a) == 0x12) printf("Big Endian"); else printf("Little Endian");
输出
Little Endian