alloc初探02:isa

本文详细介绍了C语言中的结构体和联合体,包括它们的定义、内存分配和使用特点。同时,深入剖析了Objective-C中的initIsa函数,解释了其内部逻辑,特别是如何通过cls和bits初始化isa指针,并探讨了位域的含义和作用。内容涵盖内存管理、类型转换和指针优化等方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、知识预习(结构体、联合体)

结构体

结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(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。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值