<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          iOS 底層探究之 alloc

          共 16049字,需瀏覽 33分鐘

           ·

          2022-11-15 07:37

          ????關(guān)注后回復(fù) “進群” ,拉你進程序員交流群????

          我們通過幾個問題來探究下一個iOS如何獲取到一個對象:

          1. alloc和init的區(qū)別?
          2. alloc方法做了哪些事情?

          alloc 和 init的區(qū)別

          從字面意思上,我們可以知道alloc是用來分配內(nèi)存,init是用來初始化數(shù)據(jù)。下面我們通過代碼來驗證一下:

          NSObject *obj1 = [NSObject alloc];
          NSObject *obj2 = [obj1 init];
          NSObject *obj3 = [obj1 init];
          NSObject *obj4 = [NSObject alloc];
          NSLog(@"obj1: %@, %p, %p", obj1, obj1, &obj1);
          NSLog(@"obj2: %@, %p, %p", obj2, obj2, &obj2);
          NSLog(@"obj3: %@, %p, %p", obj3, obj3, &obj3);
          NSLog(@"obj4: %@, %p, %p", obj4, obj4, &obj4);

          obj1: <NSObject: 0x6000000fc580>, 0x6000000fc580, 0x7ffee64db358
          obj2: <NSObject: 0x6000000fc580>, 0x6000000fc580, 0x7ffee64db350
          obj3: <NSObject: 0x6000000fc580>, 0x6000000fc580, 0x7ffee64db348
          obj4: <NSObject: 0x6000000fc6a0>, 0x6000000fc6a0, 0x7ffee64db340

          分析NSObject對象的打印:

          1. obj1、obj2、obj3 的內(nèi)存地址是一樣 0x6000000fc580,和obj4 0x6000000fc6a0,說明init不會分配,調(diào)用alloc時才分配了棧地址,
          2. obj1、obj2、obj3、obj4 變量的指針地址都不一樣,而且是連續(xù),依次變小的,因為指針地址分配在棧區(qū),棧區(qū)分配內(nèi)存是連續(xù)的。
          3. 棧區(qū)和堆區(qū)的內(nèi)存分配圖解:

          總結(jié):

          1. alloc才會分配內(nèi)存地址,init用于初始化數(shù)據(jù)。

          2. 變量指針地址分配在棧區(qū),而且是嚴格根據(jù)變量聲明順序連續(xù)分配內(nèi)存地址,從高到低分配。

          3. NSObject對象的內(nèi)容一般存儲在堆區(qū),從低到高分配,因為堆空間分配是找到一塊可用且大于需要分配內(nèi)存大小的地址,有可能后分配的內(nèi)存地址可能更小。

          alloc方法做了哪些事情

          從我對alloc的調(diào)用棧和實現(xiàn)邏輯,得到以下結(jié)論:

          1. 分配對象所需的內(nèi)存,并做了內(nèi)存對齊工作
          2. 將對象和所屬類型通過isa屬性綁定起來

          準備工作

          下載可編譯的objc4源碼[1],可以直接使用,不需要配置。如果斷點不生效,我的解決方案是將target -> build phases -> compile sources -> 將要斷點的文件移到最前面就生效了。

          alloc調(diào)用鏈

          1. NSObject調(diào)用alloc

          2. 調(diào)用objc_alloc

          3. callAlloc(cls, true, false)

          4. NSObject 通過objc_msgSend調(diào)用 +alloc

          5. _objc_rootAlloc

          6. callAlloc(cls, false, true)

          7. _objc_rootAllocWithZone

          8. _class_createInstanceFromZone(): 內(nèi)部實現(xiàn)內(nèi)存分配和綁定類型

            1. instanceSize(): 計算obj所需要的內(nèi)存及實現(xiàn)內(nèi)存對齊
            2. calloc(): 分配內(nèi)存,得到一個對象
            3. initInstanceIsa(): 綁定類型
          9. alloc調(diào)用流程圖:

          分配內(nèi)存,并實現(xiàn)內(nèi)存對齊

          1. instanceSize()方法提供了兩種計算內(nèi)存的方法,第一個分支走hasFastInstanceSize(), 第二個分支走alignedInstanceSize()
          inline size_t instanceSize(size_t extraBytes) const {
              if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
                  return cache.fastInstanceSize(extraBytes);
              }
              size_t size = alignedInstanceSize() + extraBytes;
              // CF requires all objects be at least 16 bytes.
              if (size < 16) size = 16;
              return size;
          }

          2.判斷是否可以快速計算實例化內(nèi)存大小。__builtin_constant_p()函數(shù)表示如果為常數(shù)返回1,如果是變量是返回0。而且在_class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC)調(diào)用時extra傳入的就是0,所以if分支為真,應(yīng)該調(diào)用 _flags & FAST_CACHE_ALLOC_MASK16。但是在實際運行中,發(fā)現(xiàn)走的是 _flags & FAST_CACHE_ALLOC_MASK。我通過 po __builtin_constant_p(extra) == 0發(fā)現(xiàn)是true,因為無法看到__builtin_constant_p的實現(xiàn),這里也就不深究了。最后結(jié)果返回的YES,所以下一步調(diào)用 fastInstanceSize().

          bool hasFastInstanceSize(size_t extra) const
          {
              if (__builtin_constant_p(extra) && extra == 0) {
                  return _flags & FAST_CACHE_ALLOC_MASK16;
              }
              return _flags & FAST_CACHE_ALLOC_MASK;
          }

          3.調(diào)用fastInstanceSize函數(shù),這里才是實現(xiàn)內(nèi)存對齊的地方。因為 po __builtin_constant_p(extra) == 0 所以走else分支,調(diào)用align16()實現(xiàn)內(nèi)存對齊。

          size_t fastInstanceSize(size_t extra) const
          {
              ASSERT(hasFastInstanceSize(extra));
              if (__builtin_constant_p(extra) && extra == 0) {
                  return _flags & FAST_CACHE_ALLOC_MASK16;
              } else {
                  size_t size = _flags & FAST_CACHE_ALLOC_MASK;
                  // remove the FAST_CACHE_ALLOC_DELTA16 that was added
                  // by setFastInstanceSize
                  return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
              }
          }

          4.align16()中對對象所需的做(x + size_t(15)) & ~size_t(15),目的很簡單,即對16取余,當有余數(shù)是,取出這部分加上16. 比如: size_t(15)是01111,取反后是10000, 如果超過16的話,前面補1。33 二進制是100001, &10000得到100000即32。

          static inline size_t align16(size_t x) {
              return (x + size_t(15)) & ~size_t(15);
          }

          5.以上在objc4實際運行的調(diào)用鏈,總結(jié)可得: iOS通過alloc分配內(nèi)存,且做了內(nèi)存對齊,對齊的字節(jié)數(shù)是16.實際上我們得對象的結(jié)尾數(shù)字不是0就是8,就是這個原因。

          6.instanceSize()方法的else分支走alignedInstanceSize()方法,最終調(diào)用word_align(),同4中分析可知對齊字節(jié)是8。

          uint32_t alignedInstanceSize() const {
              return word_align(unalignedInstanceSize());
          }

          static inline uint32_t word_align(uint32_t x) {
              return (x + WORD_MASK) & ~WORD_MASK;
          }

          #   define WORD_MASK 7UL // 64位下

          總結(jié): alloc最終通過_class_createInstanceFromZone()方法調(diào)用instanceSize()計算對象所需的內(nèi)存,在64位下進行16對齊,然后通過calloc()分配內(nèi)存。

          綁定類型

          1. alloc最終_class_createInstanceFromZone()方法initInstanceIsa()實現(xiàn)類型綁定。
          inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
          {
              ASSERT(!cls->instancesRequireRawIsa());
              ASSERT(hasCxxDtor == cls->hasCxxDtor());

              initIsa(cls, true, hasCxxDtor);
          }
          1. 然后調(diào)用objc_object::initIsa()方法,在64位機器下,isa都進行了優(yōu)化(nonpointer == 1),所以走else分支, 通過setClass()將obj和Class綁定起來
          inline void objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
          {
              ASSERT(!isTaggedPointer());
              isa_t newisa(0);

              if (!nonpointer) {
                  newisa.setClass(cls, this);
              } else {
                  ASSERT(!DisableNonpointerIsa);
                  ASSERT(!cls->instancesRequireRawIsa());

                  newisa.has_cxx_dtor = hasCxxDtor;
                  newisa.setClass(cls, this);
                  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;
          }

          總結(jié)

          綜上的現(xiàn)象,我們可知alloc()方法實現(xiàn)了對象的內(nèi)存分配,內(nèi)存對齊,將對象和類型綁定三個功能。

          內(nèi)存對齊實際案例

          1. Apple在64位下,對象內(nèi)存對齊是16,結(jié)構(gòu)體是8。

          2. 內(nèi)存分配時,會根據(jù)屬性或成員變量的類型length, 屬性或成員的起始內(nèi)存必須是該類型length的整數(shù)倍。

          驗證64位下內(nèi)存對齊是16

          1. 在內(nèi)存分配時,最終調(diào)用objc-runtime-new.h _class_createInstanceFromZone()方法中
          2. 調(diào)用順序是: _class_createInstanceFromZone() -> instanceSize() -> cache.fastInstanceSize() -> align16()
          3. 最終調(diào)用的是align16()方法, 對分配的內(nèi)存x做內(nèi)存對其, 對其規(guī)則(x + size_t(15)) & ~size_t(15)
            1. ~size_t(15): size_t(15)是01111,取反后是10000, 如果超過16的話,前面補1

            2. (x + size_t(15)) 這是為了實現(xiàn)分配的內(nèi)存不小于實際需要的,向上加一個16(計算機從0開始)

            3. (x + size_t(15)) & ~size_t(15) 在2的部分上去除余數(shù),

            4. 比如13 + 15 = 28, 最后得到16, 28 二進制是11100, &10000 得到10000即16

            5. 18 + 15 = 33 最后得到32, 33 二進制是100001, &10000得到100000即32

          inline size_t instanceSize(size_t extraBytes) const {
              if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
                  return cache.fastInstanceSize(extraBytes);
              }
              size_t size = alignedInstanceSize() + extraBytes;
              // CF requires all objects be at least 16 bytes.
              if (size < 16) size = 16;
              return size;
          }

          size_t fastInstanceSize(size_t extra) const
          {
              ASSERT(hasFastInstanceSize(extra));
              if (__builtin_constant_p(extra) && extra == 0) {
                  return _flags & FAST_CACHE_ALLOC_MASK16;
              } else {
                  size_t size = _flags & FAST_CACHE_ALLOC_MASK;
                  // remove the FAST_CACHE_ALLOC_DELTA16 that was added
                  // by setFastInstanceSize
                  return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
              }
          }

          static inline size_t align16(size_t x) {
              return (x + size_t(15)) & ~size_t(15);
          }

          對象內(nèi)存分析

          @interface LKXObjectDemo1 : NSObject {
              // isa // 8
              int age; // 4
              double hegiht; // 8
              char chr; // 1
              double weight; // 8
          }
          @end

          @interface LKXObjectDemo2 : NSObject {
              // isa // 8
              char chr; // 1
              int age; // 4
              double weight; // 8
              double hegiht; // 8
          }
          @end

          @interface LKXObjectDemo3 : NSObject {
              @public
              // isa // 8
              char chr; // 1
              int age; // 4
              int idx; // 4
              double weight; // 8
              double hegiht; // 8
          }
          @end
          1. LKXObjectDemo1 分配內(nèi)存48字節(jié),使用內(nèi)存40字節(jié),假如起始位置是0x10020000

            1. isa 占用內(nèi)存8字節(jié),起始位置是0x10020000,結(jié)束位置是0x10020007

            2. int age 占用內(nèi)存4字節(jié),起始位置是0x10020008,結(jié)束位置是0x1002000B

            3. double hegiht 占用內(nèi)存8字節(jié),起始位置也要是8的倍數(shù),所以起始位置是0x10020010,結(jié)束位置是0x10020018

            4. char chr 占用內(nèi)存1字節(jié),起始位置是0x10020018,結(jié)束位置是0x10020018

            5. double weight占用內(nèi)存8字節(jié),起始位置也要是8的倍數(shù),所以起始位置是0x10020020,結(jié)束位置是0x10020027

            6. 0x27是40,因為對象內(nèi)存對其是16,所以分配內(nèi)存48

          2. LKXObjectDemo2 分配內(nèi)存32字節(jié),使用內(nèi)存32字節(jié),假如起始位置是0x10020000

            1. isa 占用內(nèi)存8字節(jié),起始位置是0x10020000,結(jié)束位置是0x10020007

            2. char chr 占用內(nèi)存1字節(jié),起始位置是0x10020008,結(jié)束位置是0x10020008

            3. int age 占用內(nèi)存4字節(jié),起始位置也要是4的倍數(shù),起始位置是0x1002000B,結(jié)束位置是0x1002000F

            4. double weight 占用內(nèi)存8字節(jié),起始位置是0x10020010,結(jié)束位置是0x10020017

            5. double hegiht 占用內(nèi)存8字節(jié),起始位置是0x10020018,結(jié)束位置是0x1002001F

            6. 0x1F是32, 所以占用32字節(jié)

          3. LKXObjectDemo3 分配內(nèi)存48字節(jié),使用內(nèi)存40字節(jié),假如起始位置是0x10020000

            1. isa 占用內(nèi)存8字節(jié),起始位置是0x10020000,結(jié)束位置是0x10020007

            2. char chr 占用內(nèi)存1字節(jié),起始位置是0x10020008,結(jié)束位置是0x10020008

            3. int age 占用內(nèi)存4字節(jié),起始位置也要是4的倍數(shù),起始位置是0x1002000B,結(jié)束位置是0x1002000F

            4. int idx 占用內(nèi)存4字節(jié),起始位置是0x10020010,結(jié)束位置是0x10020013

            5. double weight 占用內(nèi)存8字節(jié),起始位置也要是8的倍數(shù),起始位置是0x10020018,結(jié)束位置是0x1002001F

            6. double hegiht 占用內(nèi)存8字節(jié),起始位置是0x10020020,結(jié)束位置是0x10020027

            7. 0x27是40,因為對象內(nèi)存對其是16,所以分配內(nèi)存48

          4. demo3成員變量分析,從輸出可以看出

            1. demo3(0x101b0b840)的內(nèi)存地址和chr(0x101b0b848)相差8個字節(jié), 這個8個字節(jié)就是isa的地址, demo3指向的內(nèi)存是 0x011d8001000085f9,LKXObjectDemo3 class的內(nèi)存地址是 0x00000001000085f8,剛好是后9位相同,這說明isa指向類類型內(nèi)存地址

            2. 從chr(0x101b0b848)、chr2(0x101b0b849)相隔1字節(jié),而且指向的內(nèi)存0x0000000a00003363可以看出,3的ASCII碼是33,c的的ASCII碼是63

            3. 從chr(0x101b0b848)、 age(0x101b0b84c)、idx(0x101b0b850)的內(nèi)存地址是相鄰的,而且相隔4字節(jié),說明成員屬性分配內(nèi)存必須是其類型長度的整數(shù)倍,因為int類型長度是4。因為char類型長度是1,所以沒有影響。

            4. weight(0x101c042c8)和height(0x101c042d0)各占8字節(jié)

          demo3->chr = 'c';
          demo3->age = 10;
          demo3->idx = 1;
          demo3->weight = 120;
          demo3->hegiht = 170;
          NSLog(@"chr: %p, age: %p, idx: %p, weight: %p, height: %p", &(demo3->chr), &(demo3->age), &(demo3->idx),
          &(demo3->weight), &(demo3->hegiht));

          demo3: 0x101b0b840 
          chr: 0x101b0b848, chr2: 0x101b0b849, 
          age: 0x101b0b84c, idx: 0x101b0b850, 
          weight: 0x101b0b858, height: 0x101b0b860

          0x101b0b840: 0x011d8001000085f9 0x0000000a00003363
          0x101b0b850: 0x0000000000000001 0x405e000000000000
          0x101b0b860: 0x4065400000000000 0x0000000000000000
          0x101b0b870: 0x0000000000000000 0x0000000000000000

          p [LKXObjectDemo3 class]
          (Class) $1 = 0x00000001000085f8

          struct 內(nèi)存分析

          struct StructDemo1 {
              char ch; // 1
              double height; // 8
              float weight; // 4
              char *name; // 8
              int age; // 4
          } StructDemo1;

          struct StructDemo2 {
              char ch; // 1
              int age; // 4
              char *name; // 8
              double height; // 8
              float weight; // 8
          } StructDemo2;

          struct StructDemo3 {
              struct StructDemo1 s1; // 40
              struct StructDemo2 s2; // 32
              float weight; // 4
              char chr; // 1
              int index; // 4
              double height; // 8
          } StructDemo3;
          1. StructDemo1內(nèi)存是大小是40字節(jié), 因為每個屬性都必須是其類型length,假如起始位置是0x10020000

            1. char ch 占用1字節(jié),那么ch的起始位置是0x10020000, 結(jié)束位置是0x10020000

            2. double height 占用8字節(jié),起始位置也要是8的倍數(shù),那么height的起始位置是0x10020008, 結(jié)束位置是 0x1002000F

            3. float weight 占用4字節(jié),weight的起始位置是0x10020010, 結(jié)束位置是 0x10020014

            4. char *name 占用8字節(jié),name的起始位置是0x10020018, 結(jié)束位置是 0x1002001F

            5. int age 占用4字節(jié),age的起始位置是0x10020020, 結(jié)束位置是 0x10020023

            6. 0x23是36,因為struct內(nèi)存對其是8字節(jié),所以最終分配了40字節(jié)

          2. StructDemo2內(nèi)存是大小是32字節(jié),假如起始位置是0x10020000

            1. char ch 占用1字節(jié),那么ch的起始位置是0x10020000, 結(jié)束位置是0x10020000

            2. int age 占用4字節(jié),起始位置也要是4的倍數(shù), age的起始位置是0x10020004, 結(jié)束位置是 0x10020007

            3. char *name 占用8字節(jié),name的起始位置是0x10020008, 結(jié)束位置是 0x1002000F

            4. double height 占用8字節(jié),那么height的起始位置是0x10020010, 結(jié)束位置是 0x10020017

            5. float weight 占用4字節(jié),weight的起始位置是0x10020018, 結(jié)束位置是 0x1002001B

            6. 0x1B是28,因為struct內(nèi)存對其是8字節(jié),所以最終分配了32字節(jié)

          3. StructDemo1內(nèi)存是大小是96字節(jié),假如起始位置是0x10020000

            1. struct StructDemo1 s1 占用40字節(jié), s1起始位置是0x10020000,結(jié)束位置0x10020027

            2. struct StructDemo2 s2 占用32字節(jié), s1起始位置是0x10020028,結(jié)束位置0x10020047

            3. float weight 占用4字節(jié),weight的起始位置是0x10020048, 結(jié)束位置是 0x1002004B

            4. char chr 占用1字節(jié),那么chr的起始位置是0x1002004C, 結(jié)束位置是0x1002004C

            5. int index 占用4字節(jié),起始位置也要是4的倍數(shù), index的起始位置是0x10020050, 結(jié)束位置是 0x10020053

            6. double height 占用8字節(jié),那么height的起始位置是0x10020058, 結(jié)束位置是 0x1002005F

            7. 0x5F是96,剛好使用了96字節(jié)

          補充

          為什么要內(nèi)存對齊?

          1. 平臺移植問題: 不同的硬件平臺訪問地址是有其規(guī)則,不是所有硬件都可以任意訪問所有位置。
          2. 性能問題: 數(shù)據(jù)結(jié)構(gòu)(特別是棧)應(yīng)該盡可能在自然邊界上對其。因為訪問未對齊的內(nèi)存,處理器需要做兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次。

          參考文章

          OC底層原理初探之對象的本質(zhì)(一)alloc探索上[2]

          OC底層原理初探之a(chǎn)lloc的探索上[3]

          參考資料

          [1]

          可編譯的objc4源碼: https://github.com/LGCooci/KCCbjc4_debug

          [2]

          OC底層原理初探之對象的本質(zhì)(一)alloc探索上: https://juejin.cn/post/6970693675416289287

          [3]

          OC底層原理初探之a(chǎn)lloc的探索上: https://juejin.cn/post/7056039064092278820


          來源:一吱老菜鳥

          https://juejin.cn/post/7074864288371834910

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點這里好文分享給更多人↓↓

          瀏覽 107
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜视频免费看 | 亚洲色图在线观看视频 | 中文字幕视频三区 | 91无码一区二区三区在线 | 好爽毛片一区二区三区色好美 |