Preliminary knowledge

xcrun

A tool chain for debugging or locating Xcode from the command line. To see the full documentation, type man xcrun on the command line.

It can be used to query the path of the tool, for example to find the file path of clang: xcrun -f clang.

The inputxcrun --helpYou can see all the commands xcRun supports:

Usage: xcrun [options] <tool name> ... arguments ...

Find and execute the named command line tool from the active developer
directory.

The active developer directory can be set using `xcode-select`, or via the
DEVELOPER_DIR environment variable. See the xcrun and xcode-select manual
pages for more information.

Options:
  -h, --help                  show this help message and exit
  --version                   show the xcrun version
  -v, --verbose               show verbose logging output
  --sdk <sdk name>            find the tool for the given SDK name
  --toolchain <name>          find the tool for the given toolchain
  -l, --log                   show commands to be executed (with --run)
  -f, --find                  only find and print the tool path
  -r, --run                   find and execute the tool (the default behavior)
  -n, --no-cache              do not use the lookup cache
  -k, --kill-cache            invalidate all existing cache entries
  --show-sdk-path             show selected SDK install path
  --show-sdk-version          show selected SDK version
  --show-sdk-build-version    show selected SDK build version
  --show-sdk-platform-path    show selected SDK platform path
  --show-sdk-platform-version show selected SDK platform version
Copy the code

Clang

Clang is a C/C++/Objective-C compiler written in C++ and published under the LLVM BSD license based on LLVM. You can use it to compile OC code into C++ code.

Clang-rewrite-objc main.m -o main. CPP: compiles main.m to main. CPP.

LLDB

LLDB (Low Level Debugger) is a lightweight high performance Debugger built into Xcode by default.

Common commands p and Po.

See this article for more usage.

terms

  • Big-endian mode: The low byte is stored in the high address end of the memory, and the high byte is stored in the low address end of the memory. In the little endian mode, the low byte is stored in the low address end of memory, and the high byte is stored in the high address end of memory.

  • Memory alignment: Computer systems have restrictions on where basic types of data can be stored in memory. They require the value of the first address of the data to be a multiple of some number k (usually 4 or 8). This is called memory alignment.

With the tools and terminology explained above, let’s take a look at how NSObject is implemented at the bottom.

NSObject underlying implementation

First create an NSObject object in the main function: NSObject *obj = [NSObject new]; , which is then compiled into C++ code using xcrun and clang.

Xcrun — SDK iphoneOS clang-arch arm64-rewrite-objc main.m -o main-arm64.cpp

Xcrun — SDK iphoneOS specifies the SDK as iphoneOS; Clang-arch arm64-rewrite-objc main.m -o main-arm64. CPP specifies the arm64 architecture, and compilers the OC code of main.m into C++ code and prints it to main-arm64. CPP.

In main-arm64.cpp we can see that NSObject is implemented by an NSObject_IMPL structure:

struct NSObject_IMPL {
    Class isa;
};
Copy the code

NSObject_IMPL contains only one ISA field of type Class. Class is a pointer type:

typedef struct objc_class *Class;
Copy the code

NSObject contains only one isa field of pointer type, so it actually needs 8 bytes of memory. Is the OBj system allocated 8 bytes of memory? How much memory does it actually take up?

To answer this question, clarify two concepts: actual required memory and system-allocated memory. Actual required memory refers to the memory required by the object, and the memory allocated by the system is the memory allocated by the system to the object. The memory allocated by the system >= actual required memory.

This is like the capacity of high-speed trains (memory allocated by the system) versus the actual number of passengers (memory required). Let’s say there are only 64 passengers on the bullet train today, but the number of seats on the bullet train will not change depending on the number of passengers.

First let’s see how much memory obj allocates by using the malloc_size() function:

#import <malloc/malloc.h>

NSObject *obj = [NSObject new];
NSLog(@"%zd", malloc_size(( **__bridge** **const** **void** *)(obj))); // print 16
Copy the code

You can see from the print that obj needs only 8 bytes, but the system has allocated 16 bytes to it.

The system allocates 16 bytes. How many bytes does OBj use? We can do that by size_t class_getInstanceSize(Class CLS); To learn that,

#import <objc/runtime.h>
NSLog(@"%zd", class_getInstanceSize([obj class])); // print 8
Copy the code

As you can see from the print, obj uses and needs 8 bytes, so why does the system assign 16 bytes to it? With that in mind, take a look at the source version of objC4: objC4-818.2.

allocWithZone:

Objc4 can be found in the nsobject. mm file by opening the downloaded objC4 source code:

  • AllocWithZone:
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
Copy the code
  • _objc_rootAllocWithZone
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
  • _class_createInstanceFromZone
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

Size = CLS ->instanceSize(extraBytes); Size takes the value of instanceSize.

  • InstanceSize code implementation
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

If (size < 16) size = 16; You can see that the minimum object memory footprint is 16 bytes. The comments above the code also make it clear that CF requires all objects to have a minimum memory size of 16 bytes.

So, while OBJ actually needs 8 bytes, it is allocated 16 bytes.

The following figure shows the allocWithZone call flow:

Analyze practical examples

What is the actual memory footprint and system-allocated memory for GOOD in the following code?

@interface Goods : NSObject
{
    int _count;
    NSString *_name;
}
@end


Goods *good = [Goods alloc];
NSLog(@"%zd", malloc_size((__bridge const void *)(good))); 
NSLog(@"%zd", class_getInstanceSize([good class])); 
Copy the code

Isa (8) + _count(4) + _name(8) = 20 bytes Class_getInstanceSize should return 20 bytes, but it actually does return 24 bytes. Why? This is because of memory alignment.

The structure’s memory is the largest multiple of memory for each field, and good’s ISA and name are both 8 bytes, so it should be a multiple of 8, 24 bytes.

Memory alignment

Open the project and search for class_getInstanceSize, whose implementation can be found in objc-class.mm:

size_t class_getInstanceSize(Class cls) { if (! cls) return 0; return cls->alignedInstanceSize(); }Copy the code

As you can see from the name of the alignedInstanceSize function, its purpose is memory alignment.

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

Get unaligned size with unalignedInstanceSize, memory alignment with word_align function.

  • unalignedInstanceSizeCode implementation:
uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
}
Copy the code
  • word_alignCode implementation:
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL

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

Assuming x is the 20 bytes calculated above, let’s deduce how 24 bytes is calculated:

1) (x + WORD_MASK) : 20 + 7 = 27 (binary: 0001 1011)

2) ~WORD_MASK: invert 7 to 1111 1000

3) 1111 1000&0001 1011 = 0001 1000 (24)

Calculation process:

If x is 24 bytes, the result is still 24 bytes.

  • Class_getInstanceSize call flow chart:

So what does malloc_size return? The answer is 32 bytes. The reason is that Apple’s malloc function allocates memory based on the size of a bucket. The size of the bucket is 16, 32, 48, 64, 80, etc. It can be seen that the system allocates the memory size of the object in multiples of 16.

The number of bytes of a base type under a 64-bit compiler

Char: 1 byte char*(pointer variable): 8 bytes short int: 2 bytes int: 4 bytes unsigned int: 4 bytes long: 8 bytes long long: 8 bytes unsigned long long: 8 bytes float: 4 bytes Double: 8 bytesCopy the code

conclusion

  • The underlying NSObject is implemented by NSObject_IMPL.
  • The memory actually occupied by an object is not always the same as the memory allocated by the system.
  • Memory alignment.

reference

  • Detail the big and small end mode
  • Memory alignment
  • Clang