一、知识预习(结构体、联合体)
结构体
结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member)。
结构体也是一种数据类型,它由我们自己来定义,可以包含多个其他类型的数据。
像int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体
结构体变量
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
} stu1, stu2;
理论上讲结构体的各个成员在内存中是连续存储的,和数组非常类似,例如上面的结构体变量 stu1、stu2 的内存分布如下图所示,共占用 4+4+4+1+4 = 17 个字节。但是在编译器的具体实现中,各个成员之间可能会存在空隙,C语言中,结构体大小的内存分配,参考于这片文章:类对象的内存
联合体(共用体)
结构体(Struct)是一种构造类型或复杂类型,它可以包含多个类型不同的成员。在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体(Union),它的定义格式为:
union 共用体名{
成员列表
};**
共用体有时也被成为联合体;
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
共用体也是一种自定义类型,可以通过它来创建变量,例如:
union data{
int n;
char ch;
double f;
};
union data a, b, c;
小结
联合体可以定义多个不同类型的成员,联合体的内存大小由其中最大的成员的大小决定。
联合体中修改其中的某个变量会覆盖其他变量的值。
联合体所有的变量公用一块内存,变量之间互斥。
优点: 内存使用更为灵活,节省内存。
缺点: 不够包容。
二、initIsa函数
在alloc初探最后我们写到会执行到 _class_createInstanceFromZone 函数,在这个函数里面会调用 initInstanceIsa(cls, hasCxxDtor)函数,而在这个函数下会调用initIsa函数,如图:
这里就是initIsa的实现方法
isa_t newisa(0);
啊哈!有一个isa_t,点进去 看一下,是一个联合体 自己可以点进去看一下
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0); // isa初始化
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA // isa通过cls定义
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else // bits是执行的流程
newisa.bits = ISA_MAGIC_VALUE; // bits进行赋值 0x001d800000000001ULL
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
该方法的逻辑主要分为两部分 - 通过 cls 初始化 isa - 通过 bits 初始化 isa
验证 isa指针 位域(0-64)
根据前文提及的0-64位域,可以在这里通过initIsa方法中证明有isa指针中有这些位域(目前是处于macOS,所以使用的是x86_64)
首先通过main中的LGPerson 断点 --> initInstanceIsa --> initIsa --> 走到else中的 isa初始化
还是上面这段代码 通过 cls 初始化 isa 两次后输出都是这个
(lldb) po cls
objc[24455]: mutex incorrectly locked
objc[24455]: mutex incorrectly locked
0x00000001000082c0
(lldb) p newisa // 第二次 $1变为 $2
(isa_t) $1 = {
bits = 0
cls = nil
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 0
weakly_referenced = 0
unused = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
进入 bits 初始化 isa
(lldb) po cls
MHPerson
(lldb) p newisa
(isa_t) $3 = {
bits = 8303516107965165
cls = MHPerson
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 1
shiftcls = 536875101
magic = 59
weakly_referenced = 0
unused = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
(lldb) p/t newisa
(isa_t) $5 = {
bits = 0b0000000100011101100000000000000100000000000000001000001011101101
cls = 0b0000000100011101100000000000000100000000000000001000001011101101 MHPerson
= {
nonpointer = 0b1
has_assoc = 0b0
has_cxx_dtor = 0b1
shiftcls = 0b00000000000000100000000000000001000001011101
magic = 0b111011
weakly_referenced = 0b0
unused = 0b0
has_sidetable_rc = 0b0
extra_rc = 0b00000001
}
}
对比之下发现 bits clas的值发生了变化
①newisa.bits = ISA_MAGIC_VALUE;
②magic是59(0b111011)是由于将isa指针地址转换为二进制这个是0x001d800000000001ULL
转换为16进制0x11d8001000082f0 ,从47(因为前面有4个位域,共占用47位,地址是从0开始)位开始读取6位,再转换为十进制。 具体可以看下面 注解
③shiftcls是536875101 这是在下面这份方法里运行至newisa.shiftcls = (uintptr_t)cls >> 3;前一步,其中 shiftcls存储当前类的值信息, 与bits赋值结果的对比,bits的位域中有两处变化 - cls 由默认值,变成了LGPerson,将isa与cls完美关联 - shiftcls由0变成了536871965
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
// Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
// No signing, just use the raw pointer.
uintptr_t signedCls = (uintptr_t)newCls;
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
// We're only signing Swift classes. Non-Swift classes just use
// the raw pointer
uintptr_t signedCls = (uintptr_t)newCls;
if (newCls->isSwiftStable())
signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
// We're signing everything
uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# else
# error Unknown isa signing mode.
# endif
shiftcls_and_sig = signedCls >> 3;
#elif SUPPORT_INDEXED_ISA
// Indexed isa only uses this method to set a raw pointer class.
// Setting an indexed class is handled separately.
cls = newCls;
#else // Nonpointer isa, no ptrauth
shiftcls = (uintptr_t)newCls >> 3;
#endif
}
注解
涉及方法执行流程
为什么在shiftcls赋值时需要类型强转?
因为内存的存储不能存储字符串,机器码只能识别 0 、1这两种数字,所以需要将其转换为uintptr_t数据类型,这样shiftcls中存储的类信息才能被机器码理解, 其中uintptr_t是long
为什么需要右移3位?
主要是由于shiftcls处于isa指针地址的中间部分,前面还有3个位域,为了不影响前面的3个位域的数据,需要右移将其抹零。
为什么magic要从47位开始读 为什么读6位?(对上面方法的关键部分注释)
nonpointer在0位,表示是否对isa指针开启指针优化。0:纯isa指针,1:不止是类对象地址,isa包含了类信息、对象的引用计数等。
has_assoc在1位,表示关联对象标志位,0:没有,1:有。
has_cxx_dtor在2位,表示该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
shiftcls在x86架构中占用346位,表示存储类指针的值。开启指针优化的情况下,在arm64架构中占用335位。
magic在x86架构中占用4752位,在arm64架构中占用3641位,用于调式器判断当前对象是真的对象还是没有初始化的空间。
weakly_referenced在x86架构中占用53位,在arm64架构中占用42位,标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
unused在x86架构中占用54位,在arm64架构中占用43位,标志对象是否正在释放内存。
has_sidetable_rc在x86架构中占用55位,在arm64架构中占用44位,表示当对象引用计数大于10时,则需要借用该变量存储进位。
extra_rc在x86架构中占用5663位,在arm64架构中占用4563位,当表示该对象的引用计数值时,实际上是引用计数值减1,例如:如果对象的引用计数为10,那么extra_rc为9,如果引用计数大于10,则需要使用到has_sidetable_rc。