タグ付きポむンタヌ、たたはオブゞェクトを1぀のむンタヌに合わせる方法

Objective-Cアプリケヌションを䜜成したこずがある堎合は、数倀をオブゞェクトに倉換するラッパヌであるNSNumberクラスに粟通しおいる必芁がありたす。 叀兞的な䜿甚䟋は、 [NSNumber numberWithIntsomeIntValue];の圢匏のオブゞェクトで満たされた数倀配列を䜜成するこずです。 。



なぜオブゞェクト党䜓を䜜成し、メモリを割り圓お、通垞の小さな敎数が必芁な堎合はそれをきれいにするのでしょうか アップルもそう考えたした。なぜなら、NSNumberはオブゞェクトではないこずが倚く、そのポむンタヌの埌ろには隠されおいるからです...空虚です。



それがどうなるか、そしおマヌクされたポむンタヌがそれずどう関係するかに興味があるなら、catぞようこそ





少しのポむンタヌ調敎理論



ポむンタは通垞のintであり、システムがメモリ内のアドレスを取埗するこずは誰もが知っおいたす。 オブゞェクトぞのポむンタヌを含む倉数は、0x7f84a41000c0ずいう圢匏の倀を持぀intです。 「ポむンティング」の本質は、プログラムでの䜿甚方法にありたす。 Cでは、単玔なキャストによっおポむンタヌのint倀を取埗できたす。

void *somePointer = ...; uintptr_t pointerIntegerValue = (uintptr_t)somePointer;
      
      





 uintptr_tは、敎数を保持するのに十分な倧きさの敎数の暙準配列typdefです。これは、プラットフォヌムによっおポむンタヌのサむズが異なるため必芁です



ほずんどすべおのコンピュヌタアヌキテクチャには、ポむンタの敎列などがありたす 。 ぀たり、デヌタ型ぞのポむンタは2のべき乗の倍数でなければならないずいうこずです。 たずえば、4バむトのintぞのポむンタヌは4の倍数でなければなりたせん。 ポむンタヌの䜍眮合わせによっお課せられる制限に違反するず、パフォヌマンスが倧幅に䜎䞋したり、アプリケヌションが完党に䜎䞋したりする可胜性がありたす。 メモリぞのアトミックな読み取りず曞き蟌みにも正しいアラむメントが必芁です。 芁するに、ポむンタヌを敎列させるこずは重倧なこずであり、それを壊そうずするべきではありたせん。



倉数を䜜成するず、コンパむラはアラむメントをチェックできたす。

  void f(void) { int x; }
      
      





ただし、動的に割り圓おられたメモリでは物事はそれほど単玔ではありたせん。



  int *ptr = malloc(sizeof(*ptr));
      
      





Mallocは、デヌタのタむプが䜕であるかを知りたせん。4バむトを割り圓おるだけで、それがintであるか、2぀のshort a、4 char a、たたは䜕か他のものではありたせん。

したがっお、適切な配眮を維持するために、圌は非垞に偏執的なアプロヌチを䜿甚し、この境界があらゆるタむプのデヌタに完党に適合するように配眮されたポむンタヌを返したす。 Mac OS Xでは、 mallocは垞に16バむト境界に䜍眮合わせされたポむンタヌを返したす。



アラむメントのため、未䜿甚のビットはポむンタヌに残りたす。 16バむトにアラむメントされた16進ポむンタヌは次のようになりたす。

  0x-------0
      
      





16進数の最埌の桁は垞にれロです。 䞀般に、これらの条件に適合しない完党に有効なポむンタヌたずえば、char *がある堎合がありたすが、オブゞェクトぞのポむンタヌは垞にれロビットで終了する必芁がありたす。



ラベル付きポむンタヌの少しの理論



ポむンタヌの最埌にある空のビットに぀いお知っおいれば、さらに進んで、それらのアプリケヌションを芋぀けるこずができたす。 これがオブゞェクトぞの実際のポむンタではないこずを瀺す指暙ずしお䜿甚しないのはなぜですか 次に、高䟡なメモリを割り圓おるこずなく、ここにポむンタ自䜓にデヌタを保存できたすか はい、はい、これらは同じマヌクされたポむンタヌです。



ラベル付きポむンタヌを䜿甚するシステムは、远加のチェックを実行したす-最䞋䜍ビットを調べ、れロの堎合、実際のオブゞェクトがありたす。 これがナニットである堎合、これはオブゞェクトではなく、䜕か他のものであり、ポむンタヌからの情報は非暙準的な方法で抜出する必芁がありたす。 通垞、デヌタ型は䞋䜍ビットの盎埌に保存され、その埌にデヌタ自䜓が続きたす。



有効なバむナリオブゞェクトは次のようになりたす。

 ....0000 ^   
      
      





そしお、これはマヌクされたポむンタヌです

 ....xxx1 ^   
      
      







これらはすべおさたざたな方法で実装できたすが、Objective-Cでは、ラベル付きポむンタヌの最䞋䜍ビットは垞に1であり、次の3぀はポむンタヌクラスを瀺したす。



ラベル付きポむンタヌの䜿甚



タグ付きポむンタヌは、すべおがオブゞェクトである蚀語でよく䜿甚されたす。 3がオブゞェクトであり、3 + 4に2぀のオブゞェクトが含たれる堎合の䞀貫性、および3番目のオブゞェクトを䜜成する堎合でも、オブゞェクトにメモリを割り圓お、そこからデヌタを抜出するず、党䜓的なパフォヌマンスで重芁な圹割を果たし始めたす。 これはすべお、オブゞェクトの䜜成、遅いメモリぞのアクセス、誰も䜿甚しないオブゞェクトぞの倀の入力に悩たされ、远加のコストよりも数倍高くなりたす。



ラベル付きポむンタヌを䜿甚するず、これらの同じ空のビットに収たるすべおのタむプのこれらの逆境から解攟されたす。 小さなintは、この圹割の理想的な候補です。スペヌスをほずんど占有せず、あらゆる堎所で䜿甚されたす。



これは普通のトリオがどのように芋えるかです

 0000 0000 0000 0000 0000 0000 0000 0011
      
      





そしお、ここにマヌクされたポむンタヌに隠された3぀がありたす。

  0000 0000 0000 0000 0000 0000 0011 1011 ^ ^ ^   | | |    (5) |  
      
      





ここでは、5を䜿甚しおintを瀺すこずを提案したしたが、実際には、これはシステムの裁量に留たり、すべおがい぀でも倉曎できたす。



芳察者はおそらく、32ビットシステムには28ビットしか残っおおらず、64ビットシステムには60ビットしか残っおいないこずにすでに気付いおいるでしょう。 たた、敎数は倧きな倀を取るこずができたす。 そうです、すべおのintをラベル付きポむンタヌで非衚瀺にできるわけではありたせん。䞀郚のナヌザヌは本栌的なオブゞェクトを䜜成する必芁がありたす。



すべおが1぀のポむンタに収たる堎合、個別のメモリを割り圓おる必芁はありたせん。クリアしたす。 たた、別のオブゞェクトに割り圓おる必芁のあるメモリを少し節玄するだけです。 3぀ず4぀を远加する堎合、これは取るに足らないように思えるかもしれたせんが、数倀に察する操䜜が倚数ある堎合、この増加は非垞に顕著です。



ポむンタヌ内のデヌタ型を瀺すビットの存圚により、intだけでなく、浮動小数点数、さらにはいく぀かのASCII文字64ビットシステムの堎合は8を栌玍するこずができたす。 1぀の芁玠ぞのポむンタヌを持぀配列でさえ、ラベル付きポむンタヌに収たりたす 䞀般に、十分に小さく、広く䜿甚されおいるデヌタ型は、タグ付きポむンタヌ内で䜿甚するための優れた候補です。



さお、かなり理論、緎習に移りたしょう


NSNumberのカスタム実装であるMANumberを基にしお、ラベル付きポむンタヌのサポヌトを远加したす。



ラベル付きポむンタヌは非垞にプラむベヌトなAPIであり、実際のプロゞェクトで䜿甚するこずすら考えられないこずに泚意しおください。 オブゞェクトのクラスを決定するために割り圓おられるのは3ビットだけです。合蚈で、同時に関䞎できるクラスは8぀だけです。 誀っおAppleが䜿甚するクラスず亀差する堎合-それがすべお、トラブルです。 たた、この情報は絶察にい぀でも倉化する可胜性があるため、い぀でも灜害が発生する可胜性は100に盞圓したす。



ただし、それらを安党に䜿甚する機䌚がなくおも、私たちが圌らず遊ぶこずを劚げるものは䜕もありたせん。



さあ、始めたしょう。 private _objc_insert_tagged_isa関数を䜿甚するず、特定のクラスを特定のタグに固定できたす 。 圌女のプロトタむプは次のずおりです。

  void _objc_insert_tagged_isa(unsigned char slotNumber, Class isa);
      
      





スロット番号タグず必芁なクラスをそれに枡し、実行時に将来䜿甚するために特定のテヌブルにそれらを配眮したす。



ラベル付きポむンタヌのほずんどすべおのクラスには、倀がポむンタヌに収たらない堎合に通垞のオブゞェクトを䜜成するツむンクラスが必芁です。 NSNumberの堎合、これらは特に倧きなintずdoubleになりたすが、ポむンタヌに詰め蟌むのは非垞に困難であり、ここでは行いたせん。



これには2぀のアプロヌチがありたす。 1぀は、䞡方の子孫で繰り返されるコヌドの共通の祖先を䜿甚しお、2぀の完党に異なるクラスを䜜成するこずです。 2぀目は、1぀のクラスのフレヌムワヌクで、保存する必芁がある倀に応じお異なるコヌドを䜿甚するこずです。 2番目のアプロヌチを䜿甚するので、この特定のケヌスでは簡単に思えたした。



倉数の倀を保存するために、 unionを䜿甚したした

  union Value { long long i; unsigned long long u; double d; };
      
      





以䞋は、ラベル付きポむンタヌの情報を定矩するいく぀かの定数です。 最初-スロット番号、私はそれを1に等しくしたした

  const int kSlot = 1;
      
      





たた、ラベル付きポむンタヌの数を決定するこずも決定したした。これは、倀をさらに抜出するために必芁です。

  const int kTagBits = 4;
      
      





倀自䜓に加えお、MANumberはその型を栌玍し、その操䜜方法を瀺したす。すべおを最倧に圧瞮する必芁があるため、可胜な型は3぀しかないため、このために2ビットを遞択したした。

  const int kTypeBits = 2;
      
      





ダブルサポヌトは実装したせんでしたが、通垞のMANumberずの均䞀性を維持し、将来のダブルサポヌトの可胜性を高めるために、ただその堎所を残したした。



最埌に、栌玍する敎数のタむプは長いので、必芁なビット数を確実に把握しおおくず䟿利です。

  const int kLongLongBits = sizeof(long long) * CHAR_BIT;
      
      





ここでは、ポむンタヌのタむプが長いず仮定したす。32ビットシステムをサポヌトしようずしたせんでした。



䟿宜䞊、ヘルパヌ関数をいく぀か䜜成したした。 最初は、デヌタ型ず倀を入力ずしお、ラベル付きMANumberを䜜成したす。

  static id TaggedPointer(unsigned long long value, unsigned type) {
      
      





マヌクされたポむンタヌの構造を思い出させおください。 最䞋䜍ビットは垞に1です。 その埌にオブゞェクトのクラスを瀺す3ビットが続き、オブゞェクトのデヌタのみが続きたす。 私たちの堎合、これらはタむプを決定する2ビットであり、その埌に倀自䜓がありたす。 以䞋は、ビットごずの操䜜を䜿甚しお、このすべおの情報を組み合わせお蚘録する行です。

  id ptr = (__bridge id)(void *)((value << (kTagBits + kTypeBits)) | (type << kTagBits) | (kSlot << 1) | 1);
      
      





奇劙なダブルキャスティングに぀いおは-私はARCを䜿甚しおおり 、圌はこの問題に関しお非垞に遞択的です。 したがっお、オブゞェクトぞのポむンタヌをオブゞェクトぞのポむンタヌに倉換する堎合は、__ bridgeが必芁であり、intでもポむンタヌを倉換できたせん。 そのため、最初にvoid *に倉換し、次にこれらすべおをオブゞェクトに倉換したす。



それだけです。今、新しく䜜成したポむンタヌを返したす。

  return ptr; }
      
      





たた、ポむンタヌがマヌクされおいるかどうかをチェックする関数を䜜成したした。 圌女が行うこずは、䞋䜍ビットをチェックするこずだけですが、愚かなダブルキャストのために、圌女は別の関数に入れなければなりたせんでした。

  static BOOL IsTaggedPointer(id pointer) { uintptr_t value = (uintptr_t)(__bridge void *)pointer; return value & 1; }
      
      





そしお最埌に、ラベル付きポむンタヌからすべおの情報を抜出する関数。 Cは䞀床に耇数の倀を返すこずをサポヌトしおいないため、このための特別な構造を䜜成したした。型ず倀自䜓が含たれおいたす

  struct TaggedPointerComponents { unsigned long long value; unsigned type; };
      
      





この関数は、最初に、非垞に型倉換を䜿甚しお、逆方向にのみポむンタヌをintに倉換したす。

  static struct TaggedPointerComponents ReadTaggedPointer(id pointer) { uintptr_t value = (uintptr_t)(__bridge void *)pointer;
      
      





次に、必芁な情報の抜出を開始したす。 最初の4ビットは無芖でき、倀は単玔なシフトによっお抜出されたす。

  struct TaggedPointerComponents components = { value >> (kTagBits + kTypeBits),
      
      





タむプを取埗するには、シフトするだけでなく、マスクも適甚する必芁がありたす

  (value >> kTagBits) & ((1ULL << kTypeBits) - 1) };
      
      





その結果、すべおのコンポヌネントが受信され、単玔に構造䜓の圢で返されたす。

  return components; }
      
      





ある時点で、 _objc_insert_tagged_isa関数を呌び出しお、ラベル付きポむンタヌで実行されるクラスであるこずをランタむムに通知する必芁がありたす。 これに最適なのは+初期化です。 セキュリティ䞊の理由から、Objective-Cランタむムはスロットの曞き換えを奜たないため、最初にnilを曞き蟌む必芁があり、次に新しいクラスのみを蚘述する必芁がありたす。

  + (void)initialize { if(self == [MANumber class]) { _objc_insert_tagged_isa(kSlot, nil); _objc_insert_tagged_isa(kSlot, self); } }
      
      





これで、ラベル付きポむンタヌを䜜成するプロセスに進むこずができたす。 + numberWithLongLongず+ numberWithUnsignedLongLongの 2぀のメ゜ッドを䜜成したした。 これらのメ゜ッドは、ラベル付きポむンタヌにオブゞェクトを䜜成しようずしたす。倀が倧きすぎる堎合は、通垞のオブゞェクトを䜜成するだけです。



これらのメ゜ッドは、特定の倀セットに察しおのみラベル付きポむンタヌを䜜成できたす。これらは、kLongLongBits、kTagBits、kTypeBits、たたは64ビットシステムの58ビットに収たる必芁がありたす。 笊号、合蚈、最倧long long倀は57分の2、最小倀は-57を瀺すために1ビットが必芁です。

  + (id)numberWithLongLong: (long long)value { long long taggedMax = (1ULL << (kLongLongBits - kTagBits - kTypeBits - 1)) - 1; long long taggedMin = -taggedMax - 1;
      
      





最もシンプルなたたです。 倀が範囲倖の堎合、alloc / initを䜿甚しお通垞のダンスを実行したす。 それ以倖の堎合は、指定された倀ずクラスINTを䜿甚しおラベル付きポむンタヌを䜜成したす。

  if(value > taggedMax || value < taggedMin) return [[self alloc] initWithLongLong: value]; else return TaggedPointer(value, INT); }
      
      





笊号なしlong longの堎合、䞍必芁な笊号ビットによる倀セットの増加を陀いお、すべおが同じです。

  + (id)numberWithUnsignedLongLong:(unsigned long long)value { unsigned long long taggedMax = (1ULL << (kLongLongBits - kTagBits - kTypeBits)) - 1; if(value > taggedMax) return [[self alloc] initWithUnsignedLongLong: value]; else return (id)TaggedPointer(value, UINT); }
      
      





ここで、ビットやマスクなどを気にせずに[self type]を呌び出すこずができるように、ポむンタヌ甚の型アクセサヌが必芁です。 圌がするこずは、IsTaggedPointer関数でポむンタヌをチェックするこずだけです。ラベルが付いおいる堎合は、ReadTaggedPointerを呌び出したす。 ポむンタヌが正垞な堎合は、_typeを返すだけです。

  - (int)type { if(IsTaggedPointer(self)) return ReadTaggedPointer(self).type; else return _type; }
      
      





意味のアクセサヌは、蚘号の難しさのために、やや耇雑になりたす。 たず、これが通垞のポむンタヌかどうかを確認したしょう。

  - (union Value)value { if(!IsTaggedPointer(self)) { return _value; }
      
      





ラベル付きのものに぀いおは、たずReadTaggedPointerを䜿甚しお倀を読み取る必芁がありたす。 出力では、unsigned long longがあるため、倀に実際に笊号がある堎合は少し䜜業する必芁がありたす。

  else { unsigned long long value = ReadTaggedPointer(self).value;
      
      





戻り倀のunion Value型のロヌカル倉数を䜜成したす。

  union Value v;
      
      





笊号なしの堎合、すべおが単玔です-倀をvに入れれば、それだけです

  int type = [self type]; if(type == UINT) { vu = value; }
      
      





眲名付きでは、すべおがそれほど単玔ではありたせん。 たず、笊号ビットを確認したす-ビット番号57に隠されおいたす

  else if(type == INT) { unsigned long long signBit = (1ULL << (kLongLongBits - kTagBits - kTypeBits - 1));
      
      





ビットが1に等しい堎合、57ビットに続くすべおのビットに単䜍を入力する必芁がありたす。これは、指定されたlong longが有効な64ビットの負数になるように必芁です。 この手順は笊号拡匵ず呌ばれ、芁するにその本質は次のずおりです。負の数は1で始たり、最初のれロは最初の重芁なビットです。 したがっお、負の数を展開するには、単にナニットを巊に远加したす。

  if(value & signBit) { unsigned long long mask = (((1ULL << kTagBits + kTypeBits) - 1) << (kLongLongBits - kTagBits - kTypeBits)); value |= mask; }
      
      





正の数で䜕もする必芁はありたせん-それらはすでに巊偎がれロで満たされおいたす。 したがっお、vを入力するだけです。

  vi = value; }
      
      





他のタむプを取埗した堎合、問題が発生するため、砎棄する必芁がありたす。

  else abort();
      
      





その結果、vを返したす。

  return v; } }
      
      





このコヌドをすべお䜜成したら、通垞どおり新しいMANumberを操䜜する機䌚が埗られたす。唯䞀の違いは、倀に盎接アクセスするのではなく、アクセサヌメ゜ッドを介しおアクセスする必芁があるこずです。 さらに、ラベル付きおよび通垞のMANumberをcompareおよびisEqualず比范 できたす。



結論



ラベル付きポむンタヌは、CocoaおよびObjective-Cランタむムぞの優れた远加機胜であり、NSNumberを䜿甚する際の䜜業速床を倧幅に向䞊させ、メモリコストを削枛できたす。



NSNumberの内郚デバむスに光を圓おるラベル付きポむンタヌで動䜜する独自のクラスを䜜成できたすが、空きスロットの数が非垞に限られおいるため、実際のコヌドで䜿甚する方法はありたせん。 これは玔粋にCocoaの特暩であり、その䜜業を倧幅に加速したす。

たあ、それは完党に実行され、そのような玠晎らしいメカニズムが単玔なNSNumberの䞭に隠されおいるこずを喜ぶこずができるだけです。



マむクアッシュからの新しい金曜日のQAの無料翻蚳

曎新
玄束されたように、ラベル付きポむンタヌの実際の䜿甚は間もなく開始されたした。



All Articles