Object pointer address and memory

Let’s start with the following code:

    HPerson * p1 = [HPerson alloc];
    HPerson * p2 = [p1 init];
    HPerson * p3 = [p1 init];
    
    NSLog(@"%@-%p", p1, p1);
    NSLog(@"%@-%p", p2, p2);
    NSLog(@"%@-%p", p3, p3);
Copy the code

Will there be any difference between these three?

P1, P2, p3 print the same memory address:

So in this process we can come to a conclusion:

1. Alloc gives objects memory space and Pointers to them.

Init does not do anything to the pointer.

Let’s look at the following code:

    HPerson * p1 = [HPerson alloc];
    HPerson * p2 = [p1 init];
    HPerson * p3 = [p1 init];
    
    NSLog(@"%@-%p-%p", p1, p1, &p1);
    NSLog(@"%@-%p-%p", p2, p2, &p2);
    NSLog(@"%@-%p-%p", p3, p3, &p3);
Copy the code

Perform:

It is found that three different pointer addresses point to the same memory space, and they are three consecutive pointer addresses.

So how does alloc allocate memory? Does init really do nothing?

Two, explore the bottom three methods

If we jump directly to the code:

I’m going to go to NSObject’s alloc method:

Unable to find method implementation found!

Why didn’t it happen? How do we check the alloc method in action?

Method 1: Breakpoint debugging

Let’s start with a breakpoint at alloc:

Then press CTRL and click Step into:

You can see,allocThe method calls theobjc_allocMethods:

Then we put the sign breakpoint:

Click to continue with the program:

Then we’ll see that objc_alloc comes from libobjc.a.dylib, the underlying objC dynamic library method:

Method two: follow up step by step with assembly

Open assembly in the Debug selection bar:

Still put a breakpoint at alloc:

Running the program will take you to the assembly page:

On this page, you see that the objc_alloc method is called, and then you use a symbolic breakpoint to see which dynamic library the method uses.

Method three: symbolic breakpoints through known methods

When the program stops at our breakpoint:

Add symbolic breakpoints for known methods:

Then enter:

You’ll find that alloc comes from the libobjc.a.dylib dynamic library:

That’s the basic way to explore the bottom layer in 3, disassembly, LLDB, stack, and so on.

Three, assembly combined with source code debugging analysis

1. Download the source code

Now that we have located the alloc in the libobJC dynamic library, it is time to go into the source code for debugging.

Download the source code from Apple’s open source website or, more conveniently, from Apple’s source directory.

Take the source directory as an example:

Enter the site and search for objc:

Go to ObjC4 and click on it:

There is a problem with the latest objC4-824.tar. gz and it cannot be downloaded, so objC4-818.2.tar. gz is used instead.

2, view the source code

Open the download of ObjC4-818.2 and search alloc {:

The alloc method is called _objc_rootAlloc.

Call _objc_rootAlloc again:

// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code

Find callAlloc in _objc_rootAlloc.

Call callAlloc again:

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false){#if __OBJC2__
    if(slowpath(checkNil && ! cls))return nil;
    if(fastpath(! cls->ISA()->hasCustomAWZ())) {return _objc_rootAllocWithZone(cls, nil);
    }
#endif

    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
Copy the code

So this is the core method of alloc.

__OBJC2__ refers to version 2.0, which is used today.

So here, do I say _objc_rootAllocWithZone? Do you want to do objc_msgSend?

3. Assembly debugging

Go back to the original code and put a breakpoint at alloc, where the _objc_rootAlloc symbol breakpoint is added:

Run:

It does get into the _objc_rootAlloc method, but it gets into the _objc_rootAlloc method of HPerson’s parent class NSObject. We should get into the HPerson method.

_objc_rootAlloc will need to be broken and run again. When the HPerson alloc method is broken, add the _objc_rootAlloc breakpoint:

After the break, the _objc_rootAllocWithZone method is executed before the objc_msgSend method.

4. Source code debugging

Set objC4-818.2 to iOS_objC4-756.2.

Create targets (HObjectBuild) and create HPerson (HPerson) in objC source code:

Create a breakpoint inside main.m and run the program:

Enter the _objc_rootAllocWithZone method seen above:

NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}
Copy the code

Enter the _class_createInstanceFromZone method:

/*********************************************************************** * class_createInstance * fixme * Locking: none * * Note: this function has been carefully written so that the fastpath * takes no branch. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    if(slowpath(! obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if(! zone && fast) { obj->initInstanceIsa(cls, hasCxxDtor); }else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if(fastpath(! hasCxxCtor)) {return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}
Copy the code

In the _class_createInstanceFromZone method, we found that obj was returned, so we put a breakpoint on obj:

Continue with the program:

Obj already has an address, because the current memory is not used, is dirty memory address!

Click Step over to go to calloc and the memory address is unchanged:

After clicking step over and executing calloc method, obj memory address is different:

Obj is assigned a memory address in the calloc method.

The type of obj object is id. This is because this address is not bound to our HPerson class. The associated class is ISA!

Obj is already associated with the HPerson class after initInstanceIsa:

So let isa associate HPerson with c++ methods and functions in initInstanceIsa!

The next step is to return obj, and all the alloc process is done!

Byte alignment

Re-run the _class_createInstanceFromZone method

size = cls->instanceSize(extraBytes);
Copy the code

Break point:

Additional bytes are found to be0.

Then go to the instanceSize method:

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;
    }
Copy the code

Make a breakpoint at return and continue:

If the first return is entered, there is a cache.

Then enter the fastInstanceSize method:

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
            returnalign16(size + extra - FAST_CACHE_ALLOC_DELTA16); }}Copy the code

Make a breakpoint at return and continue:

The second one wentreturnAnd,sizefor16.

So where does this figure come from?

If there is no cache, alignedInstanceSize is used and the size of byte aligned memory is returned:

What determines the size of an object’s memory?

Only member variables determine!

We enter the unalignedInstanceSize method:

// May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }
Copy the code

Find that the size is determined by instanceSize, i.e. the size of the instance variable!

Page 4 of 4 depending on class’s ivars.

And that is the size of clean memory for compilation!

So it depends on the size of the member variable!

The current HPerson object has no member variables, so the size returned here is 8:

Why 8? Because NSObject has a member variable isa:

Why is isa of size 8?

Because Class is a structure! Isa isa structure pointer, so it’s 8 bytes!

We can search for objc_class in the source code:

Found that Class is a structure pointer type!

And objc_class inherits from objc_Object, which means everything is an object! That class is also an object!

Returns a size of 8 bytes or 16 if less than 16 bytes:

So 16 bytes!

What if it’s greater than 16?

It does byte alignment!

Enter the alignedInstanceSize method:

// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
Copy the code

There is the word_align method:

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

This is the byte alignment algorithm!

Point into WORD_MASK:

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif
Copy the code

WORD_MASK = 7!

If (x + WORD_MASK) & ~WORD_MASK = 8:

  (x + WORD_MASK) & ~WORD_MASK
= (8 + 7) & ~7
= 15 & ~7Convert to binary:15To:0000 1111
7To:0000 0111
~7To:1111 1000So:15 & ~7
= 0000 1111 & 1111 1000
= 0000 1000Converted to decimal:8
Copy the code

But when it’s 9, it’s 16!

So this is an 8-byte alignment, an integer multiple of 8 up!

Why is it a multiple of 8?

Because the maximum is a pointer, 8 bytes! Also for space for time, convenient memory read!

V. Brief flow chart of Alloc

For those of you who are interested, you can check out my next article, “The OC Object Principle explores internal Preservation and alignment.”