This is the first day of my participation in the More text Challenge. For details, see more text Challenge
The pointer address and memory of the alloc object
To get started, let’s create a project, create a new class Person, and look at the results of the code below
Person *p1 = [Person alloc];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
NSLog(@"%@--%p--%p", p1, p1, &p1);
NSLog(@"%@--%p--%p", p2, p2, &p2);
NSLog(@"%@--%p--%p", p3, p3, &p3);
Copy the code
The final print result is:
<Person: 0x2809ec5b0>--0x2809ec5b0--0x16f9f1b58
<Person: 0x2809ec5b0>--0x2809ec5b0--0x16f9f1b50
<Person: 0x2809ec5b0>--0x2809ec5b0--0x16f9f1b48
Copy the code
We find that the results are exactly the same, from which we can draw the following conclusions: \
alloc
A block of memory is createdinit
No action is done on the current pointerp1
p2
p3
The addresses of the three Pointers are consecutive, and the three addresses are different, pointing to the same memory space
As shown in the figure below:
So, how does alloc open up memory? Does init really do nothing? So what does it do?
To answer these questions, we need to look at the underlying implementation of alloc, but when we click we can’t look at the actual implementation of alloc, so we need to think of other ways to explore how the underlying implementation of alloc is implemented.
There are three approaches to low-level exploration
- 1.
control
+step into - 2, symbol breakpoint location view call flow
- 3, assembly view call flow
control
+step into
1, We are inPerson *p1 = [Person alloc];
Put a breakpoint on the code and run the project
2. At this point, we hold downcontrol
Button, and clickstep into
button
At this point, we come to the objc_alloc method, which is shown on the real machine, but the simulator doesn’t show the difference
We can’t get any more information on the next step, and at this point, we need to add a symbolic break point for objc_alloc
Symbol breakpoint location View the call flow
And then we move on, and we see that we’re in the implementation of objc_alloc, and we also know that we’re going to execute _objc_rootAllocWithZone
We can also continue with the process by adding a symbolic breakpoint for _objc_rootAllocWithZone
Assembler view the call flow
In addition to the above two methods, we have a third method that uses assembly to view the flow of the call (first remove the symbol breakpoints added earlier)
Set a breakpoint and run the project
2. Open the Assembly windowDebug
->Debug Workflow
->Always Show disassembly
3, continue tocontrol
+step into
Perform toobjc_alloc
In the
Then, add the corresponding symbol breakpoint and continue to see the calling process. When the symbol breakpoint is executed again, we can know where the current symbol breakpoint is in the source code
In addition, we can also add the alloc symbol breakpoint to debug directly
Now that we know where the method is in the source code, we can explore the underlying invocation process more directly through the source code
Assembly combined with source debugging analysis
Apple Source download address: Apple Open Source Source Browser
Let’s explore the object C4-818.2 source code as an example. As we already know, [Person alloc] will call the [NSObject alloc] method first, so we can find the implementation of this method in the source code:
As we go further and further, we see that the method call flow is alloc->_objc_rootAlloc->callAlloc, and then we see that in callAlloc, the code has a judgment branch call
Alloc, _objc_rootAlloc,callAlloc,callAlloc,callAlloc
Run the project to the [Peson alloc] breakpoint, where three symbol breakpoints are opened:
Executing the code, we find that the call order is as follows:
Deeper calls can be stepped into by themselves
During the execution of the breakpoint, we noticed that the callAlloc symbol was not broken. Why?
There is a concept of compiler optimization
Compiler optimization
What is compiler optimization? Let’s look at this code:
At this point, we switch to the Assembly window:
Continue executing assembly code:
Next, we execute to the end of the sum function:
We come to the conclusion that after a series of assembly operations, the result is returned in the w0(X0) register and we will look at the operation after we have optimized the compiler
Set compiler optimizations
In Debug mode, the compiler is not optimized by default. In Release mode, the compiler is optimized to Fastest,Smalest[-os]
We will beDebug
Mode compiler optimizations are also adjustedFastest,Smalest[-Os]
Then run the project and switch to the assembly window
We find that there is no process to call the sum method. The compiler directly stores the operation result of the sum method in the W8 register. This is the result of compiler optimization
Alloc’s main flow
1. Preparation
Create the Person class in the source code project and call the [Person alloc] method in the main method
2, breakpoint execution
We go through alloc, objc_rootAlloc, and finally callAlloc
3. Continue to execute
Call _objc_rootAllocWithZone
There are two macros slowPath :#define slowPath (x) (__builtin_expect(bool(x)), 0)) fastpath:#define fastpath(x) (__builtin_expect(x), 1) Allow the programmer to tell the compiler which branch is most likely to execute __builtin_expect((x),1) means that the value of x is more likely to be true. __builtin_expect((x),0) means that the value of x is more likely to be false
Depending on the meaning of the macro definition, the objc_rootAllocWithZone method will most likely be executed, and it does:
Then enter the_class_createInstanceFromZone
methods
4,cls->instanceSize
Figure out how much memory space you need(16 bytes)
5,calloc
Applying for memory Space
We initialize obj, the system allocates a dirty memory space for us by default, memory data will only be overwritten but not cleared
After executing the calloc method, the system has applied for new memory space for us. According to the printed information, we find that the current obj is not associated with our Person, so we continue to execute
6,initInstanceIsa
willobj
withPerson
The binding
After obj->initInstanceIsa(CLS, hasCxxDtor) is executed, obj is bound to CLS
At this point,alloc
The underlying invocation logic has been completed, and the flowchart is drawn as follows:
Init and new analysis
init
Through source code analysis
Init directly returns obj in the source code without doing anything else
Init is a factory pattern that acts as a destructor and is overridden by subclasses to provide an interface for easy extension
new
New is equivalent to the alloc init operation
Byte alignment and its principles
CLS ->instanceSize calculates the required memory size, so how do you calculate the required memory size? We enter this method:
Breakpoint executing[Person alloc]
We can see thatextraBytes
A value of0
, then the required memory sizesize
Just byalignedInstanceSize
Decision:
So unalignedInstanceSize, which is 8 bytes, comes from Class ISA in Person’s parent NSObject
So 8 bytes is what if you get the final size of 16 bytes? Let’s look at the implementation of word_align:
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
Copy the code
#define WORD_MASK 7UL
So the calculation becomes
(8 + 7) & ~7 is 15 & ~7Copy the code
The calculation process is as follows:
0000 1111 (binary of 15) 0000 0111 (binary of 7) 1111 1000 (binary of ~7) 15 & ~7 with the operation below 0000 1111 1111 1000 the result 0000 1000 is converted to binary 8Copy the code
Like (8 + 7) >> 3 << 3
AlignedInstanceSize = 8
If (size < 16) size = 16, the result is 16
Object memory space
How much memory does an object take up?
The size of memory occupied by an object is determined by its member variables
The verification process is as follows:
Delete all breakpoints from the project
Print that the object takes 8 bytes, (memory data is aligned with 16 bytes)
2. Add an attribute
3. Continue adding properties
Conclusion: The more member variables, the more memory they occupy
So where did Job1 go?
X /4gx P: Prints the object P in 4 formatted typesets
More explanation:
X /nuf <addr> n indicates the number of memory units to be displayed. ------------------------ u indicates the length of an address unit. B indicates a single byte ------------------------ f indicates the display mode. The value can be: X displays the variable in hexadecimal format d displays the variable in decimal format U displays the unsigned integer in decimal format O displays the variable in octal format t displays the variable in binary format A displays the variable in hexadecimal format I displays the variable in instruction address format c displays the variable in character format F Displays variables in floating point formatCopy the code