Memory layout
- Stack: Local variables, methods, parameters, functions, method Pointers, generally starting with 0x7, access — accessed through registers
- Heap: a place where space needs to be created. Objects allocated by alloc, block,copy. Generally starting with 0x6, access – by object -> heap address -> pointer to stack
- The program loads into memoryUsually starts with 0x1
- Uninitialized data (.bss)
- Initialized data (.data)
- Code snippet (.text) Program code that is loaded into memory
- Kernel area: system call
static
Modified member variables do not occupy memory. Static applies to the global partition and occupies the memory of the global partition, but does not occupy the heap in which the structure resides
Memory management scheme -TaggedPointer
First of all, what is Tagged
- 1: TaggedPointer is used to store small objects such as NSNumber and NSDate, NSString/NSIndexPath
- 2: The value of the TaggedPointer pointer is no longer an address, but a real value. So, it’s not really an object anymore, it’s just a normal variable in an object’s skin. Therefore, its memory is not stored in the heap and does not require malloc and free
- 3. Memory read (objc_msgSend) is 3 times more efficient and 106 times faster than before.
The structure of the common object pointer on the ARM64 is 0x00000001003041e0.
We have 64 bits, but we don’t really use all of them, we only use the middle bits in a real object pointer. Because of alignment requirements, the low position is always 0 and the object must always be at an address that is a multiple of the size of the pointer. Because the address space is limited, the high level is always 0, we don’t actually use 2^64, these high and low levels are always 0.
Selecting one of these bits, where the high and low levels are always 0, and setting it to 1, lets us know that this is not a true object pointer, and gives the other positions some additional meaning. We call this pointer Tagged; for example, we can insert values in other bits
101010 is 42 when converted to base 10. An identifier 1 is added to the end to indicate that this is Tagged, and the system can then treat these objects as Pointers to objects. Instead of assigning a small number object to each of these situations, which are actually confused with random values initialized at process startup, this security makes it difficult to fake a Tagged margin
Set the low value to 1 to indicate that it is a Tagged pointer and distinguish it from the real pointer. The next 3 bits are the tag number. So this is the type of tagged pointer, so for example, 3 means it’s an NSNumber,6 means it’s an NSDate, and since there’s a three-bit tag there are eight possible tag types, and the rest is payload and this is the type of data that you can use anywhere you want.
Now tag 7 has a special case where it represents an extended tag. The extended note can use the next 8 bits to encode the type, which means 256 more tag types at the cost of reducing the Payload
For the above features, it can be used to store some user interface UIColor and NSIndexSets
If you’re a Swift developer, you can create your own Tagged Pointers. If you’ve ever used an enumeration with associated values, that’s a class like Tagged Pointer. The Swift runtime stores the enumeration discriminator in an alternate bit of the associated value payload. And Swift’s use of value types actually makes Tagged Pointer seem less important. Because the value no longer needs to be exactly the size of a pointer, for example the Swift UUID type can be two words and remain inline, rather than assigning a separate object because it does not fit inside a pointer. This is Tagged Pointer on Inter
Tagged pointers on ARM64
On ARM64 these are reversed, with the highest bit set to 1 instead of the lowest bit being usedTagged pointer
And then the next 3 bits are the tag bits. WhyARM64
How about this design?
This is actually a little optimization for objc_msgSend, we want the path of objc_msgSend to be as fast as possible, and the most common path is a plain pointer, and we have two less common cases called Tagged Pointer and nil
It turns out that when we use the highest bit, we can check both cases at once, as opposed to checking Tagged Pointer and nil separately, saving a conditional branch for the common case in msgSend.
As with Inter, the next 8 bits are used as extension tags, and the rest is the payload, which is actually the old format used in iOS13. Apple made some changes in iOS14 in 2020: Keep the tag bit at the top, because msgSend’s optimization is still very useful, moving the tag number to the bottom three bits, which will occupy the top eight bits after the tag bit if you are using an extended tag
Why do you do that? If we look at normal Pointers, our existing tools like dynamic linking will Ignore the first 8 bits of the pointer, which is a Top Byte Ignore feature of ARM. Instead, we will place the extension tag in the Top Byte Ignore bit. For an aligned pointer, the last 3 bits are always 0. You just add a little number to the pointer, 7 which is an extension label that changes the low value to 1
This means that we can actually put the pointer above into the payload of an extended tag pointer, and the result is a Tagged Pointer that has a normal pointer in its payload. Why is this useful? It turns on Tagged Pointer, the ability to reference constant data in binary files, such as strings or other data structures. Otherwise they will have to eat up dirty Memory
The correct way to do this is to use APIisKindOfClass, etc. These Tagged Pointer items can be retrieved using standard APIS. This also applies to CF types
Under the x86-64Tagged Pointer
structure
Objc_debug_taggedpointer_obfuscator = objc_debug_taggedPOinter_obfuscator = objc_debug_taggedPointer_obfuscator = taggedPointer
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
return (void *)ptr;
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag);
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= permutedTag << _OBJC_TAG_INDEX_SHIFT;
#endif
return (void *)value;
}
Copy the code
So in the above code we can xor an objC_debug_taggedPOinter_obfuscator to get the real value
uintptr_t
kc_objc_decodeTaggedPointer(id ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
Copy the code
Let’s print them out separatelyNSString``NSNumber
As well asNSIndexPath
Compared withOBJC_TAG
Type table, which corresponds exactly to.
ARM64
Under theTagged Pointer
structure
As mentioned above, in iOS14 and beyond, the tag bits are the last three bits, not the firstSo, how do we print out the above code on a real machine? We saw when we obfuscated the code that if! DisableTaggedPointerObfuscation
Turn off obfuscation, global searchDisableTaggedPointerObfuscation
static void
initializeTaggedPointerObfuscator(void)
{
if(! DisableTaggedPointerObfuscation) {// Pull random data into the variable, then shift away all non-payload bits.
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
#if OBJC_SPLIT_TAGGED_POINTERS
// The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);
// Shuffle the first seven entries of the tag permutator.
int max = 7;
for (int i = max - 1; i >= 0; i--) {
int target = arc4random_uniform(i + 1);
swap(objc_debug_tag60_permutations[i],
objc_debug_tag60_permutations[target]);
}
#endif
} else {
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
objc_debug_taggedpointer_obfuscator = 0; }}Copy the code
LibObjc global search locates here:
OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION, "disable obfuscation of tagged pointers")
Copy the code
So we’re in Xcodescheme
On the configurationNow that it’s running, there’s no confusion,NSString
andNSNumber
(refer to the console printout above)
TaggedPointer
Related interview questions
Which of the following codes will crash?
- (void)taggedPointerDemo {
self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<100; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"cooci"];
NSLog(@ "% @".self.nameStr); }); }}Copy the code
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@ "come");
// Multithreaded reading and writing
// setter -> retian release
for (int i = 0; i<100; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"cooci_ learn harmoniously without being impatient"];
NSLog(@ "% @".self.nameStr); }); }}Copy the code
During multiple operations (multiple clicks on the screen), we found that the second code crashed because the first nameStr type was NSTaggedPointerString
The second nameStr is of type **NSCFString**
The reason for this is that the type changes from TP to OC when the character length is >=10
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return (id)this;
// ...
}
Copy the code
Multi-thread reading is a process of retain and release. We can see from the source code that if it is a small object, it will be returned directly, so there is no wild pointer. This means small objects are not managed by ARC memory at all
Retain and release
Retain process, we open the libObjc source to locate this method
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
Copy the code
Extra_rc: When representing the referential count value of this object, which is actually the referential count value minus 1, the following hash table has_sideTABLE_rc is used if the location is required to be full.
- In the first place to judge
isTaggedPointer
- judge
nonpointer
If it’s anonpointer
Is the operation on the ISA bit,- not
nonpointer
Hash table operations reference count (mainly because there is noneextra_rc
In thenonpointe_isa
The object’s reference count is stored in the “. - Is it being released?
- is
nonpointer
Directly toisa.bits
Bit operation findingextra_rc
To add and subtractretain/release
) operationnewisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
- If it’s full, in the real world
uintptr_t extra_rc : 8
Can store 2^8=256 sizes. If it’s full, it finds a new place to store it, and that place is the hash table, and the hash table is 1/2
- not
Release process: As with the above process, the only thing to note is that after the hash table is also decrement, the dealloc operation is performed
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc))
Copy the code