Nature of object

Exploring the nature of OC objects, we usually write objective-C code, the underlying implementation is actually C\C++ code. So how much memory does an OC object take up? After reading this article you will understand the memory layout and memory allocation mechanism of OC objects.

Use code to download tools to use:

  • Xcode 10.2
  • gotoShell
  • Linux – glibc 2.29 source code
  • Libmalloc source

First of all, let’s use basic code to verify what is the object?

int main(int argc, const char * argv[]) {
	@autoreleasepool {
	    // insert code here...
		NSObject *obj=[[NSObject alloc]init];
	    NSLog(@"Hello, World!");
	}
	return 0;
}
Copy the code

CPP is generated by executing clang-rewrite-objc main.m -o main.cpp. This generated CPP is not known which platform it runs on. Now we specify iphoeos and arm64 to compile it again. Xcrun -sdk iphoneOS clang -arch arm64-rewrite-objc main.m -o main64. CPP, drag main64. CPP into Xcode and open.

clang The compiler
xcrun The command
sdk Specify the platform for compilation
arch Arm64 architecture
-rewrite-objc rewrite
main.m Rewritten file
main64.cpp Exported files
-o export

Command + F finds the key code for int main, which is converted to C/C ++ :

int main(int argc, const char * argv[]) {
 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

  NSObject *obj=((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

     NSLog((NSString *)&__NSConstantStringImpl__var_folders_c0_7nm4_r7s4xd0mbs67ljb_b8m0000gn_T_main_1b47c1_mi_0);
 }
 return 0;
}
Copy the code

And then the search

struct NSObject_IMPL {
	Class isa;
};
Copy the code

So what is this structure? After compiling Object -c, the Object will be compiled into a structure, as shown in the following figure:

isa

typedef struct objc_class *Class; 
Copy the code

Class is a pointer to a structure. Com + click class to get:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if ! __OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
Copy the code

Class is a pointer, so how much memory does it take up? Everyone knows that Pointers are 4 bytes in 32 bits and 8 bytes in 64 bits.

NSObject *obj=[[NSObject alloc]init];
Copy the code

We can think of the instance object as a pointer, and the pointer takes up 8 or 4 bytes. Obj is a pointer to the structure class. So let’s verify:

int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSObject *obj=[[NSObject alloc]init]; Size_t size = class_getInstanceSize(obj.class); // The size of the NSObjet pointer to the memory.#import <malloc/malloc.h>
		size_t size2 = malloc_size((__bridge const void *)(obj));
		NSLog(@"size:%zu size2:%zu",size,size2);
	}
	return 0;
}
Copy the code

The results are:

size:8 size2:16
Copy the code

The result: the pointer is 8 bytes, and the size of the memory to which the pointer points is 16 bytes. [[NSObject alloc]init] [[NSObject alloc]init]

class_createInstance
    -_class_createInstanceFromZone
Copy the code
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if(! cls)return nil;
    **
    size_t size = cls->instanceSize(extraBytes);
    **
    return obj;
}

Copy the code

Size = CLS ->instanceSize(extraBytes) CLS ->instanceSize CLS ->instanceSize

// Member variable size 8bytes uint32_talignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }
    
    size_t instanceSize(size_t 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

CF requires all objects to be at least 16bytes.

Class_getInstanceSize -> CLS ->alignedInstanceSize

// Member variable size 8bytes uint32_talignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }
Copy the code

So the final conclusion is: the actual size of the object pointer is 8bytes, and the memory allocation is 16bytes, which is actually 8bytes empty.

Debug->Debug Workflow->View Memory

obj->view *objc
address
obj

Use the LLDB command memory read 0x100601f30 to output the memory layout as shown in the following figure:

x/4xg 0x100601f30

x/4xg 0x100601f30
4
4
x
g

So let’s use a more complex object to verify:

@interface Person : NSObject
{
	int _age;
	int _no;
}
@end
Copy the code

Iphoneos clang-arch arm64-rewrite-objc main.m-o main64. CPP

struct NSObject_IMPL { Class isa; }; struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; // 8 bytes int _age; //4 bytes int _no; //4 bytes };Copy the code

The Person — IMPL structure takes up 16bytes

Person *obj=[[Person alloc]init];
		obj->_age = 15;
		obj->_no = 14;
Copy the code

Verify with code:

		Person *obj=[[Person alloc]init];
		obj->_age = 15;
		obj->_no = 14;
		
		struct Person_IMPL *p =(__bridge struct Person_IMPL*)obj;
		NSLog(@"age:%d no:%d",p->_age,p->_no);
		
		//age:15 no:14
Copy the code

Use memory layout validation:

Person

Here’s an intuitive memory layout:

Let’s look at the memory layout for more complex inheritance relationships:

@interface Person : NSObject { @public int _age; //4bytes } @end @implementation Person @end //Student @interface Student : Person { @public int _no; //4bytes } @end @implementation Student @endCopy the code

The Student member variable _no must be an integer multiple of 16 due to memory alignment. That’s 16+16=32 bytes. Student is an object. There is no memory alignment between member variables and Pointers. Parameters and Pointers are derived from object Pointers + offsets. So Student takes 16 bytes.

So let’s prove it:

Student *obj=[[Student alloc]init]; obj->_age = 6; obj->_no = 7; ->8 size_t size = class_getInstanceSize(obj.class); ->16 size_t size2 = malloc_size((__bridge const void *)(obj)); NSLog(@"size:%zu size2:%zu",size,size2);
		//size:16 size2:16
		
		
Copy the code

Take a look at the memory layout that LLDB looks at:

(lldb) x/8xw 0x10071ae30
0x10071ae30: 0x00001299 0x001d8001 0x00000006 0x00000007
0x10071ae40: 0xa0090000 0x00000007 0x8735e0b0 0x00007fff

(lldb) memory read0x10071ae30 0x10071ae30: 99 12 00 00 01 80 1d 00 06 00 00 00 07 00 00 00 ................ 0x10071ae40: 00 00 09 a0 07 00 00 00 b0 e0 35 87 ff 7f 00 00 .......... 5... (lldb) x/4xg 0x10071ae30 0x10071ae30: 0x001d800100001299 0x0000000700000006 0x10071ae40: 0x00000007a0090000 0x00007fff8735e0b0Copy the code

You can see that 0x00000006 and 0x00000007 are the values of the two member variables, which occupy 16 bytes of memory.

We add a member variable to Student:

//Student @interface Student : Person { @public int _no; //4bytes int _no2; //4bytes } @end @implementation Student @endCopy the code

Then look at the memory layout:

(lldb) x/8xg 0x102825db0
0x102825db0: 0x001d8001000012c1 0x0000000700000006
0x102825dc0: 0x0000000000000000 0x0000000000000000
0x102825dd0: 0x001dffff8736ae71 0x0000000100001f80
0x102825de0: 0x0000000102825c60 0x0000000102825890

Copy the code

As you can see from the LLDB, the memory has become 32 bytes. (0x102825dd0-0x102825db0=0x20)

Let’s add one more property:

@interface Person : NSObject { @public int _age; //4bytes } @property (nonatomic,assign) int level; //4 bytes @end@implementation Person @end //InstanceSize:16 malloc_size:16Copy the code

Why is it that when an attribute is added, the memory remains the same as when it was not added? Property =setter+getter+ivar,method = ivar,method = ivar,method = ivar,method = ivar,method = ivar,method = ivar

Now, what is the number of member variables when we have 3? The three int member variables are 12, one pointer is 8, and the last one is 20. Since the memory is a multiple of 8, it is 24.

@interface Person : NSObject { @public int _age; //4bytes int _level; //4bytes int _code; //4bytes } @end @implementation Person @end Person *obj=[[Person alloc]init]; obj->_age = 6; Class ocl = obj.class; // Get the size of NSobject instance member variables ->24 Class ocl = obj.class; size_t size = class_getInstanceSize(ocl); NSObjet ->32 size_t size2 = malloc_size((__bridge const void *)(obj));printf("InstanceSize:%zu malloc_size:%zu \n",size,size2);
		
InstanceSize:24 malloc_size:32
Copy the code

Why is it different from what we thought? So let’s explore: How much memory does the instance object take up, of course, when it was created, Then look for the source nsobject.mm AllocWithZone->_objc_rootAllocWithZone-> _objc_rootAllocWithZone->class_createInstance->_class_createIns TanceFromZone ->_class_createInstanceFromZone = _class_createInstanceFromZone

id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if(! cls)return nil;
**
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;
    **
    obj = (id)calloc(1, size);
   **
    return obj;
}

Copy the code

So let’s look at the instanceSize implementation:

// The size of the object pointer is uint32_talignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

    size_t instanceSize(size_t 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

Obj = (id)calloc(1, size); The value passed in is 24, but the result is 32 bytes of memory requested, why? Because this is a c function, let’s go to the Apple open source website to download the source code, you can find this code:

#define NANO_MAX_SIZE			256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */
Copy the code

NANO_MAX_SIZE is optimized to be a multiple of 16 when applying for space, with a maximum of 256. So size = 24; obj = (id)calloc(1, size); The result of the request is 32 bytes. And then what is the mechanism of Linux space requisition? Download gnu materials and get:

#ifndef _I386_MALLOC_ALIGNMENT_H
#define _I386_MALLOC_ALIGNMENT_H

#define MALLOC_ALIGNMENT 16

#endif /* ! defined(_I386_MALLOC_ALIGNMENT_H) */


/* MALLOC_ALIGNMENT is the minimum alignment for malloc'ed chunks. It must be a power of two at least 2 * SIZE_SZ, even on machines for which smaller alignments would suffice. It may be defined as larger than this though. Note however That code and data structures are optimized for the case of 8-byte alignment. */ // At least 2 times SIZE_SZ or __alignof__(long) double) #define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \ ? __alignof__ (long double) : 2 * SIZE_SZ) /* The corresponding word size. */ #define SIZE_SZ (sizeof (INTERNAL_SIZE_T)) #ifndef INTERNAL_SIZE_T # define INTERNAL_SIZE_T size_t #endifCopy the code

__alignof__ (long double) is 16 in iOS, and size_t is 8. This code is short to #define MALLOC_ALIGNMENT (2*8 < 16? 16:2*8) is finally 16 bytes.

Conclusion:

The instance object is actually a structure that takes up at least 16 multiples of 16 memory. Due to memory alignment, the actual memory used is M, so the actual allocated memory is (M%16+M/16)*16. The size of the instance object is not affected by the method, but by the instance variable.

  • Download of learning materials
  • The demo to see