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: \

  1. allocA block of memory is created
  2. initNo action is done on the current pointer
  3. p1 p2 p3The 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 downcontrolButton, and clickstep intobutton

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 intoPerform toobjc_allocIn 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 beDebugMode 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_createInstanceFromZonemethods

4,cls->instanceSizeFigure out how much memory space you need(16 bytes)

5,callocApplying 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,initInstanceIsawillobjwithPersonThe binding

After obj->initInstanceIsa(CLS, hasCxxDtor) is executed, obj is bound to CLS

At this point,allocThe 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 thatextraBytesA value of0, then the required memory sizesizeJust byalignedInstanceSizeDecision:

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