Things come, things go

Finding problems:

  1. objcCan’t compile? Debug problems?
  2. alloc -> objc_alloc ?
  3. instanceSizeSize calculation? 8 + 8 +4 -> 32, why not 24?
  4. x/4gx ?
  5. [LGPerson alloc]Source code compilation, why run firstobjc_alloc(Class cls){... }Phi is a function of phi, not phi directly+ (id)alloc {... }???

A,ObjcCompilation problems

1. Select compilation SettingsKCObjcBuild

2. The compilation fails

①: Build Settings -> enable hardened runtime -> NO

②: Build phase -> denpendenice -> objc

3. M1 computer error solved

The wrong location

The solution

  • annotationobjc-cache.mmIn the filevoid cache_t::init()Part of the code in a functionLine 1067, line 1068As follows:
_objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",

                kr, mach_error_string(kr));
Copy the code
  • annotationobjc-runtime-new.mmNumber one in the documentLines 176 and 177The code block
STATIC_ASSERT((~ISA_MASK & MACH_VM_MAX_ADDRESS) == 0  ||  

              ISA_MASK + sizeof(void*) == MACH_VM_MAX_ADDRESS);
Copy the code

Second,LLVMTo optimize theallocTo optimize the

1. allocTo mark a symbol breakpoint

2. View assembly code

Objc alloc (); objC alloc ();

3. The analysisallocOrder of function calls

3.1 Analysis Results

Objc-runtime-new. mm file, find imp where alloc is modified in fixupMessageRef. View the fixupMessageRef function called in _read_images (the current file of the class’s load map). The _read_images call fixupMessageRef is used to fix the message sending information, according to the comments of the call function

Deducing further from _read_images, we can get the following call order:

  • fixupMessageRef <- _read_images <- _objc_init_image
  • fixupMessageRef <- _read_images <- map_images_nolock <- map_images <- _dyld_objc_notify_register <- _objc_init

The _objc_init call is performed by the dyLD loading application, so the hook is required prior to this compilation phase

3.2 throughllvmvalidationallocThe process of
  • Search for the objc_alloc method in the llVM-project project

    If the current function returns true, some selectors call the corresponding entry point:

    alloc => objc_alloc

    allocWithZone:nil => objc_allocWithZone

  • [Foo alloc] -> objc_alloc(Foo)

  • Why can I walk to EmitObjCAlloc? Did the hooks in the compile phase to alloc, create meet the conditions for the first time, will be executed tryGenerateSpecializedMessageSend function sends a message mechanism, back to the previous step OMF_alloc conditions, [Foo alloc] -> objc_alloc(Foo); The underlying LLVM after receiving the message will not perform tryGenerateSpecializedMessageSend to send, and perform GenerateMessageSend common message is sent, perform the normal process [LGPerson alloc] – > alloc

  • Flow chart analysis

Three, the object memory influence factors

< span style = “box-sizing: border-box; color: RGB (51, 51, 51); font-size: 13px! Important; word-spacing: 0px;”

Class_getInstanceSize: The actual memory size of the object, which is determined by the size of the class’s member variables.

24The reason?

LGPerson inherits NSObject and has an 8-byte ISA that also takes up space. So the minimum amount of memory needed is 8 + 8 + 8 = 24, which is exactly 8-byte alignment.

Continue adding fields

(int age, double height, char c1, char c2)

Print result:40The reason?

(ISA)8 + (NSString *name)8 + (NSString *nickName)8 + (int age)4 + (double height)8 + (char c1)1 + (char c2)1 = 38; 8 bytes aligned, so the output is 40

Add a member variable to the object and continue printing

@interface LGPerson : NSObject {
    NSString *nickName
}
@property (nonatomic, copy) NSString *name;
@end
Copy the code

Print result: 24; 8 plus 8 plus 8 is 24

Add another method to the object and continue printing

@interface LGPerson : NSObject {
    NSString *nickName
}
@property (nonatomic, copy) NSString *name;
+ (void)sayNB;
@end


@implementation LGPerson
+ (void)sayNB{
}
@end
Copy the code
Print result: 24Copy the code

Results analysis

Attributes and member variables all affect the memory size of the current object. Method does not affect, the method does not exist in the current object memory.

  • Memory structure analysis:

Conclusion:

The memory of age, C1, and C2 is optimized to occupy 8 bytes of floating point number. The output is p/f ‘ ‘e -f f — 0x0XXXXXX

Memory pointer printing mode:

4. Structure memory alignment

  • The actual memory size of the object is 8 bytes aligned. Highlights memory alignment.

    c oc 32 – A 64 – bit
    bool BOOL(64位) 1 1
    signed char (_ _signed char)int8_t, BOOL(32 bits) 1 1
    unsigned char Boolean 1 1
    short int16_t 2 2
    unsigned short unichar 2 2
    int int32_t NSInteger(32 bits), Boolean_T (32 bits) 4 4
    unsinged int Boolean_t (64 bits), NSUInteger(32 bits) 4 4
    long NSInteger(64位) 4 8
    unsigned long NSUInteger(64位) 4 8
    long long int64_t 8 8
    float CGFloat(32位) 4 4
    double CGFloat(64位) 8 8

Memory alignment causes:

Memory is based on bytes, while CPU accesses data in blocks rather than bytes. Frequent access to unaligned data can significantly degrade CPU performance. Byte alignment reduces the number of CPU accesses. The purpose of this space – for – time swap is to reduce CPU overhead.

Memory alignment principles

1, Data member alignment rules: the first data member of a struct (or union) is placed at offset 0, and the starting position of each data member is from the size of the member or the size of the member’s children (as long as the member has child members, such as arrays). Structure, etc.) (for example, if int is 4 bytes, it is stored from the address that is a multiple of 4. Min (current starting position m n) m = 9 n = 4 9 10 11 12

2, Struct as members: If a struct contains members, then the members are stored at multiples of the size of the largest element in the struct.

The total sizeof a structure, the result of sizeof, must be an integer multiple of its largest internal member. What is lacking must be made up.

Case analysis

  • Ordinary struct

  • Normal struct attribute exchange order

    struct LGStruct1 {
        double a;       // 8    [0 7]
        char b;         // 1    [8]
        int c;          // 4    (9 10 11 [12 13 14 15]
        short d;        // 2    [16 17] 24
    }struct1;
    
    struct LGStruct2 {
        double a;       // 8    [0 7]
        int b;          // 4    [8 9 10 11]
        char c;         // 1    [12]
        short d;        // 2    (13 [14 15] 16
    }struct2;
    
    struct LGStruct21 {
        int b;          // 4    [0 1 2 3 4]
        double a;       // 8    (5 6 7) [8 9 10 11 12 13 14 15]
        char c;         // 1    [16]
        short d;        // 2    (17) [18 19] 24
    }struct21;
    Copy the code

    Print result:

    Sizeof is a keyword that is a compile-time operator used to determine the byte sizeof a variable or data type. The sizeof operator can be used to get the sizeof classes, structures, Commons, and other user-defined data types.

    Struct1, struct2, struct21 contain the same attribute variables, but the order is not the same, caused by the memory size is not consistent. According to? -> Internal alignment of structure.

    The following analysis is made according to the internal alignment of the structure:

    1, struct1

    variable Take up the byte offset min location
    double a 8 0 min(0, 8) 0 ~ 7
    char b 1 8 min(8, 1) 8
    int c 4 12 min(12, 4) 12 ~ 15
    short d 2 16 min(16, 2) 16 ~ 17

    According to the data member alignment rules, the first one should start from 0, and the starting position of each child member should start from an integer multiple of the size of the child member. In this step, the size of the occupied byte of struct1 is 17. However, the maximum variable a in struct1 takes up 8 bytes, and the actual memory size of struct1 must be an integer multiple of 8. 17 is not a multiple of 8, and the final memory size of struct1 is rounded up to 24

    2, struct2

    variable Take up the byte offset min location
    double a 8 0 min(0, 8) 0 ~ 7
    int b 4 8 min(8, 4) 8 ~ 11
    char c 1 12 min(12, 1) 12
    short d 2 14 min(14, 2) 14 ~ 15

    According to the data member alignment rules, the first one should start from 0, and the starting position of each child member should start from an integer multiple of the size of the child member. In this step, the size of the occupied byte of struct2 is 15. However, the maximum variable a in struct2 accounts for 8 bytes, and the actual memory size of struct2 must be an integer multiple of 8. 15 will not be a multiple of 8. Rounded up, the final memory size of struct2 is 16

    3, LGStruct21

    variable Take up the byte offset min location
    int a 4 0 min(0, 4) 0 ~ 3
    double b 8 8 min(8, 8) 8 to 15
    char c 1 16 min(16, 1) 16
    short d 2 18 min(18, 2) 18 ~ 19

    According to the data member alignment rules, the first one should start from 0, and the starting position of each child member should start from an integer multiple of the size of the child member. In this step, the size of the occupied byte of struct21 is 19. However, the maximum variable a in struct21 is 8 bytes, the actual memory size of struct21 must be an integer multiple of 8, 19 will not be a multiple of 8, rounded up, the final memory size of struct21 is 24.

  • Structure nested case column analysis

    struct LGStruct1 {
        double a;       // 8    [0 7]
        char b;         // 1    [8]
        int c;          // 4    (9 10 11) [12 13 14 15]
        short d;        // 2    [16 17] 24
    }struct1;
    
    struct LGStruct3 {
        double a;                 // 8    [0 7]
        int b;                    // 4    [8 9 10 11]
        char c;                   // 1    [12]
        short d;                  // 2    (13) [14 15]
        int e;                    // 4    [16 17 18 19]
        struct LGStruct1 str;     // 24   [24 47]
    }struct3;
    Copy the code
  • Print result:

    • Struct3 analysis results are as follows

      variable Take up the byte offset min location
      double a 8 8 min(0, 8) 0 ~ 7
      int b 4 8 min(8, 4) 8 ~ 11
      char c 1 12 min(12, 1) 12
      short d 2 14 min(14, 2) 14 ~ 15
      int e 4 16 min(16, 4) 16 ~ 19
      str.a(double) 8 24 min(24, 8) 24 and 31
      str.b(char) 1 32 min(32, 1) 32
      str.c(int) 4 36 min(36, 4) 36 ~ 39
      str.d(short) 2 40 min(40, 2) 40 ~ 41
      • Struct LGStruct1 STR, struct lgt1 STR, struct lgt1 STR, struct lgt1 STR, struct lgt1 STR, struct lgt1 STR, struct lgt1 STR We start at 24.

    • The final size of struct3 is 48, combining integer multiples of the largest internal member 8

5. Malloc source code introduction

  • Case analysis

    @interface LGPerson : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *nickName;
    @property (nonatomic, assign) int age;
    @property (nonatomic, assign) long height;
    @end
    Copy the code

    1. Sizeof is an operator that retrieves the sizeof a type (int, size_t, structure, pointer variable, and so on) at compile time. Person is a structure pointer, so it is 8 bytes

    2. Class_getInstanceSize is a function that is acquired at runtime. The actual memory used by the created object plus all instance variables is aligned with 8 bytes

      • The LGPerson member size is28.8Byte alignment, member size32In the addisaThe final size is 40
    3. Malloc_size: the size allocated to the heap, the size requested by the system. Malloc functions on Mac and iOS always allocate memory in multiples of 16, i.e. hexadecimal alignment

Vi.malloc_sizethelibmallocSource code analysis

  1. Void *p = calloc(1, 40); NSLog(@”%lu”,malloc_size(p)); NSLog(@”Hello, World!” );

    The result is printed as' 48 '. Why?Copy the code
  2. The trace code goes into the _malloc_zone_calloc function and looks for key parts of the code based on the return value PTR, as follows:

  3. PTR = zone->calloc(zone, num_items, size); In this line of code, Po prints the zone->calloc function (if there is an assignment, the stored value will be printed), and the terminal will print the next function to execute default_zone_calloc

  4. Global search default_zone_calloc function, continue breakpoint debugging, Po print zone->calloc, nano_calloc function appears

  5. Continue searching for the nano_calloc function, go to the nano_calloc function, go to the current method, locate the code at _nano_malloc_check_clear.

  6. Enter the _nano_malloc_check_clear function and analyze the code execution flow.

The size Settings of interest are locked to the key generation at line 621, which goes into the code

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
    size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
    *pKey = k - 1; // Zero-based!
    return slot_bytes;

}
Copy the code
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
Copy the code

Calculation process:

Where size=40, NANO_REGIME_QUANTA_SIZE=16 hexadecimal alignment k = (40 + 16-1) >> 4; Move 4 bits to the left to get k=56 and then move 4 bits to the right. Slot_bytes =48 is just hexadecimal!