Advanced iOS — An exploration of objects and structures

preface

At the end of this article, memory alignment is discussed in iOS Advanced — Exploration of underlying principles of Alloc, but there is no detail about why memory should be aligned, and the factors affecting object memory are not explored. This article will explore the following aspects:

  • Factors that affect object memory
  • Principles and understanding of structure alignment
  • How does the system read assembly validation of a structure
  • Design significance of alignment rules

First, the exploration of object memory

1.1 Factors affecting the memory size of an object

First create an iOS project and create a BPPerson class (which will follow us for a long time 😊). Let’s take a look at the properties associated with this class

The size of the print object in the console is now 32We open the comments separately and addheightMember variables, and inBPPerson.maddweightProperty, then print the result as followsWe can see that adding attributes and member variables will affect the memory size of an object. We can analyze the reasons for these two values:

The OC object will contain an isa by default, which isa Class and a pointer to a structure, so it will have 8 bytes first and then analyze the attributes

  • When commenting height and weight:

    • Name: 8 bytes
    • Title: 8 bytes
    • Age: 4 bytes
    • InCharacter: 1 byte
    • OutCharacter: 1 byte

    In this case, there are 30 bytes in total. After byte alignment, there are 32 bytes

  • Uncomment height and weight:

    • Height: 8 bytes
    • Weight: 4 bytes

    Add 12 bytes to make 42 bytes, and align to 48 bytes.

We know that the properties of objects in OC will generate the corresponding member variables by default, so we use @dynamic to disable age from generating member variables. Again, we observe the following results:

From this observation, when the attributeageWhen member variables are no longer generated, memory becomes 40. Therefore, we find that attributes affect memory because member variables are generated, which means that it is the member variables that ultimately affect the memory size of the object. The question that follows is, does the method affect the size of memory? We are inBPPersonAdd a class method and an instance method, and look again at the result:

We can see that the result is exactly the same as the last print; neither class methods nor instance methods affect object memory.

Conclusion: It is the member variables that affect the memory size of the object, the reason that the attribute affects the memory size is that the member variables are generated, and the method has no effect on the memory size of the object.

1.2 Object memory distribution and optimization

While the previous section explored the factors that affect memory, this section explores the distribution of object memory. The size of an object’s memory is affected by its member variables, and its memory is also composed of member variables, as shown in the figure below:The Person pointer points to an area of memory where the object is stored, including ISA and other member variables. So the question is, how are these member variables distributed, are they stored sequentially? Let’s explore with BPPerson as an example.

First of all inBPPersonInternal toweightAssign a value of 75, the code is very simple, I won’t show it here, and the other properties are assigned as follows:Then use thex/8gx personTo look at the memory (x/8gx means to display in hexadecimal, 8-byte groups), the result is as follows:

  • : the left side is the memory address, : the right side is the stored value
  • 0x28279AE20 is the first address of the object, followed by 0x000021a104a3D615 is ISA
  • After 0x4066C00000000000 is height, floating point numbers need to be viewed with p/f
  • Age, inCharacter, and outCharacter are merged to 0x0000001200004241
  • 0x000000000000004b is weight, followed by name and title

From this, we find that the memory distribution is not completely arranged in order, but some optimization will be done. For example, age, inCharacter and outCharacter will be combined together, because they are less than 8 bytes in total, while name and title are arranged in order. This is the system to help us do the optimization, its purpose is to facilitate the improvement of access efficiency at the same time, can optimize storage, reduce waste.

2. Alignment rules and verification in vivo

The last section focused on the memory of an object, but in OC, the essence of an object is actually a structure, which will be explored in subsequent articles, and this section continues to explore the memory alignment of structures.

First of all, start with a question. Define two structures as follows. Do the two structures occupy the same memory size? If not, what are they?

typedef struct {
    double a; 
    char   b; 
    int    c; 
    short  d; 
} BPStruct;

typedef struct {
    double a; 
    int    b; 
    char   c; 
    short  d; 
} BPStruct1;
Copy the code

The result, I think, is that they both have the same memory size, 16 bytes. However, the results, 👏👏 hit the face, here is the results:

2.1 Introduction to the alignment rules of the structure

Why does this happen? In fact, the memory of the structure has its own alignment rules. Of course, these rules are used to improve the read efficiency. The specific rules are as follows:

  • The first member of a struct must start at offset 0, and all subsequent members must start at an integer multiple of the size of that member’s type. If a member has children, such as an array, it must start at an integer multiple of the size of its children

  • 2. If structure A contains another structure B, then B starts at an integer multiple of the type size of the largest member in B

  • 3. The final size of a structure is an integer multiple of the type size of its largest member, or an integer multiple of Max (its own maximum member size, the substructure’s maximum member size) if it contains substructures

Based on this rule, let’s analyze why the answers to the above questions are 24 and 16. Before the analysis, put a picture of the size of each basic type in bytes, which can be referred to during the analysis. Also assume that a tag start indicates the location where the member starts to be stored.

First take a look at BPStruct. The analysis steps are as follows:

typedef struct { double a; // double: 8 bytes first member start = 0, store location [0 7] char b; // char: 1 byte 8 is a multiple of 1 start = 8, store location [8 9 10 11] int c; // int: 4 bytes 12 is a multiple of 4 start = 12, store location [12 13 14 15] short d; // short: 2 bytes, 16 is a multiple of 2 start = 16, store location [16 17] // [0 17] total 18 bytes, but not double size, that is a multiple of 8, the final size is 24} BPStruct;Copy the code

Take a look at BPStruct1, and the analysis steps are as follows:

typedef struct { double a; // double: 8 bytes first member start = 0, store location [0 7] int b; // int: 4 bytes 8 is a multiple of 4 start = 8, store location [8 9 10 11] char c; // char: 1 byte 12 is a multiple of 1 start = 12, store location [12] short d; // short: 2 bytes 13 is not a multiple of 2, jump a start = 14 store location [14 15] // [0 15] total 16 bytes, 16 is a multiple of 8, the final size is 16} BPStruct1;Copy the code

If we create a new structure, we will see how this rule applies to a structure that contains a substructure. The following figure shows the structure and the execution result:

The STR in this structure is the previous STRBPStruct1, you can see that the printed result is 32. The following are the reasons for this result combined with the rule analysis:

typedef struct { double a; // double: 8 bytes first member start = 0, store location [0 7] char b; // char: 1 byte 8 is a multiple of 1 start = 8, store location [8 9 10 11] short c; // short: 2 bytes 12 is a multiple of 2 start = 12, store location [12 13] int d; / / int: 4 bytes 14 and 15 are not multiples of 4, skip, start = 16, storage location [19] 16 17 18 BPStruct1 STR. // The maximum member is 8 bytes, 20, 21, 22, 23 are not multiples, jump, start = 24 // The size is calculated to be 24 + 16, a total of 30 bytes, but needs to be the maximum member multiples of 8, the final result is 32 bytes} BPStruct2;Copy the code

2.2 How to read constructs for assembly validation

Section 2.1 is mainly an introduction to the rules of structure, but is the system really read in this way? Let’s write a function, then break to the assembly, and analyze the structure reading process. The structure is BPStruct1 and BPStruct2 in Section 2.1, and the functions are as follows:

–> Debug overflow –> Always show Disassembly

  • In the assembly analysis before a brief introduction of some assembly instructions and related knowledge:
    • IOS is a small-endian system. The stack space of functions is opened up from the high address to the low address, and data is read to the high address. Sub sp, sp, #0x30; add sp, sp, #0x30;
    • In the figure above, SP represents the top of the stack, x0, X1, w10 and so on represent registers under ARM. A register has 8 bytes, and there are 32 general purpose registers under ARM, namely X0 ~ X31. W represents the lower 32 bits of the register, and w0 is the lower 32 bits of x0
    • Sub: subtracting instruction, in the figure sp = sp – #0x30, STR/STRB means to write the value of the register into the stack memory, LDR means to write the data in the stack memory into the register, mov can be simply regarded as the assignment instruction, in the figure MOV x8, sp means x8 = sp
    • In the figure, [] can be understood as a segment of address, such as STR x0, [sp #0x20] indicates that the value of the x0 register is written to the location of the memory address [sp #0x20], 0x represents hexadecimal, 0x8 represents hexadecimal 8,0 x10 represents 16, hexadecimal full hexadecimal one is 0x10, And so on 0x20

Let’s start by analyzing the code in the figure. The register read register is used to read the value of the register:

1, the figure is the code break point, this time just entered the function, that is, the initial state, let’s first analyze the assembly code above line 8. This part of the code does several things

0x100a1e264 <+0>: sub sp, sp, #0x30 ; 0x100A1e268 <+4>: STR x0, [sp, #0x20] STR x0, [sp, #0x20] [sp, #0x28] 0x100a1e270 <+12> Mov x8, sp; mov x8, sp; mov x8, sp; Mov x9, #0x900000000000 0x100a1e278 <+20>: movk x9, #0x4066, LSL #48Copy the code

The current state is as follows:

2. Next, we analyze the part of assembly code line 8 ~ 14, which corresponds to the member assignment of BPStruct2, and is a key part of observing how the structure is read

0x100a1e27c <+24>: STR x9, [sp] / / will x9 value that is assigned to 180.5 sp registers the stack, the corresponding BPStruct2 s.a = 180.5, namely the sp deposit has a value of 180.5 0 x100a1e280 < + > 28: Mov w10, #0x61 // 0x61 = 0x100a1e284 <+32> w10: STRB w10, [x8, #0x8] STRB w10, [x8, #0x8] [sp, #0x8] [sp, #0x8] [sp, #0x8] [sp, #0x8] [sp, #0x8] [sp, #0x8] Mov w10, #0x6 // w10 0x100a1e28c <+40> STRH w10, [x8, #0xa]; [sp, #0x8]; [sp, #0x8]; [sp, #0x8] [sp, #0x8] [sp, #0x8] [sp, #0x8] [sp, #0x8] [sp, #0x8] [sp, #0x8] [sp, #0x8] 0x100a1e290 <+44>: 0x100a1e290 <+44>: 0x100a1e290 <+44>: 0x100a1e290 <+44>: 0x100a1e290 <+44>: #0x5a STR w10, [sp, #0xc] // [sp, #0xc] // So you don't need to offset the direct storage to take up four bytesCopy the code

At this time, the status diagram and analysis are as follows:

3. The last part is the processing of the structure STR. The first member of STR a = 18.5, and q0 in this part is found to be a register, but different from the general register, it has 16 bytes

0x100a1e298 <+52>: LDR q0, [sp, #0x20] LDR q0, [sp, #0x20] LDR q0, [sp, #0x20] STR q0, [sp, #0x10]; STR q0, [sp, #0x10]; STR q0, [sp, #0x10]; Which is exactly an integer multiple of the size of the largest double inside STRCopy the code

At this time, the state diagram is as follows:

Through the analysis of the three parts of the assembly code, we can find that STR starts from [sp #0x10] storage, to [SP #0x20] is exactly 16 bytes, plus the previous 16 bytes, a total of 32 bytes, which can verify the structure of the principle of internal storage, the system is indeed stored in accordance with this rule.

3. Design significance of alignment rules in structure

To explore a knowledge point is to know how and why. As mentioned in section 2, the alignment rules in the structure are designed to improve the reading efficiency of the structure, but how does this design improve the efficiency? So let’s explore that a little bit.

Take BPStruct as an example, look at the unaligned and aligned cases respectively, and the size of the occupied space in the two cases is 15 and 24 respectively:

typedef struct {
    double a; 
    char   b; 
    int    c; 
    short  d; 
} BPStruct;
Copy the code

First, let’s take a look at how members are read, assuming there are no memory alignment rules and no memory alignment.

If there is no memory alignment, the size of each member is the size of its type, and the starting position does not have to be an integer multiple of its size, followed by the last member. The storage results are shown as follows:The result is a smaller memory footprint, but there are a few disadvantages in reading:

  • 1. Each read needs to calculate the size of the space to be read according to the size of the current member before reading, which undoubtedly reduces the reading efficiency
  • 2. Assume that a scale is set when reading. If the maximum size of A is taken as the scale and 8 bytes are read each time, when reading B, the read space will exceed the space of the structure, and error reading is easy to occur
  • 3. If a member is read in a different size, an incomplete read will occur. For example, if a member is read in a char size of 1 byte, no other member will be read complete.

In short, when the alignment is misaligned, it will be very inconvenient to read. Here’s how memory alignment works:

This aligned approach will find the largest metric, and then use that metric to read such a large length each time. This has the following advantages:

  • 1. When the maximum length is read, the memory space of the structure is not exceeded, because the total length is a multiple of the maximum length

  • 2. Each member starts with its own multiple, which ensures that when reading at the maximum scale, complete data can be read each time, and the number of reads will be greatly reduced compared with one by one

Structure after alignment, although take up some big storage space, but in reading efficiency greatly reduce, case is not aligned to read four times, each time to recount how much space to read, use alignment need only read the two times, each time 8 bytes can be read, it is a space for the ideas of the time, From this, we can see the significance of the alignment of structure in vivo.

conclusion

This article explores two things:

1. The memory distribution of objects and the factors affecting the memory size of objects

2. Rules, verification and significance of the alignment of structures in vivo

This article is the end of the exploration, welcome to read, if there are found mistakes or deficiencies, welcome to criticize.