Internal alignment of structures

Introduction: we all know that in iOS development, we write oc code, the bottom is to use c++ to achieve, and oc object is structure Pointers, so the structure of the calculation of memory is what, there are any rules, we will study the following.

First, let’s take a look at the following two structures and print out how much memory they take up to see what happens.

struct Struct1 { double a; // 8 bytes char b; // 1 byte int c; // 4 bytes short d; // 2 bytes} struct1; struct Struct2 { double a; // 8 bytes int b; // 4 bytes char c; // 1 byte short d; // 2 bytes} struct2;Copy the code
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSLog(@"struct1 size : %lu \n struct2 size : %lu", sizeof(struct1), sizeof(struct2));
}
Copy the code

We can see that the members of both structures are of the same type, but in different order. The results:

The result really blew us away! So why is the order different? Let’s see

Principles of internal alignment of structures:

  1. Data member alignment: 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 its children (as long as the member has children, such as arrays). Structure, etc.) (for example, if int is 4 bytes, it is stored from the address that is a multiple of 4.
  2. Struct as members: If a structure has some struct members, the structure members are stored from an integer multiple of the size of the largest element in the structure.
  3. Wrap up: the total sizeof the structure, the result of sizeof, must be an integer multiple of the largest member within the structure.

Now that we’ve seen the alignment principle, let’s verify why this is different.

struct Struct1 { double a; // 8 bytes [0...7] char b; // 1 byte [8] int c; // 4 bytes (9,10,11,[12...15] short d; // 2 bytes [16,17]} struct1; Struct Struct2 {double a; // 8 bytes [0...7] int b; // 4 bytes [8...11] char c; [12] short d; // 2 bytes (13,[14,15]} struct2; // 8 bytes memory alignment 16 -> 16Copy the code

And that’s exactly what we saw. Let’s make it harder:

struct Struct3 { double a; // 8 bytes [0...7] int b; // 4 bytes [8...11] char c; [12] short d; // 2 bytes (13,[14,15] int e; // 4 bytes [16...19] struct Struct1 s1; // struct (20,21,22,23,[24...47]); // 8 bytes memory alignment 48 -> 48Copy the code

If there is a structure nested, we calculate the memory size of struct3 should be 48 bytes according to the above rules, we print the verification result:

Let’s do another case

struct Struct4 { char a; // 1 byte [0] short b; // 2 bytes [2,3] double c; // 8 bytes [8...15] int d; // 4 bytes [16...19]} struct4; Struct Struct5 {int a; // 4 bytes [0...3] int b; // 4 bytes [4...7] struct Struct4 s4; // 24 bytes [8...31] short c; // 1 byte [32]}struct5; Struct Struct6 {int a1; // 4 bytes [0...3] int b1; // 4 bytes [4...7] char a; // 1 byte [8] short b; // 2 bytes [10,11] double c; // 8 bytes [16...23] int d; // 4 bytes [24...27] short e; // 1 byte [28]}struct6; // 8 bytes of memory aligned 28 -> 32Copy the code

C++ structures can be inherited, but struct5 and struct6 are different, because in the process of inheritance, it can be understood as inheriting the small organization of the parent structure. The memory allocation in the parent structure is the same. Even if there is extra memory in the parent structure, the child structure does not have the permission to write data to it. So their memory footprint is different.

Since structure inheritance is like this, let’s try classes in OC.

OC class isa pointer to the structure, so SJFather is 16 bytes (isa->8 bytes, a->1 bytes, 16 bytes aligned), SJSon inherited SJFather, if the above structure situation, is not the first SJFather inherited 16 bytes and has no permission to modify. Plus a b->1 byte, 16 bytes aligned make up 32 bytes. Isa ->8 + a->1 + b->1 = 10,16 bytes aligned with 16 bytes.

OC Object memory size

Let’s look at the memory size of the object.

@interface SJPerson : NSObject

@property (nonatomic, copy) NSString *name;  // 8
@property (nonatomic, copy) NSString *nickName;  // 8
@property (nonatomic, assign) int age;  // 4
@property (nonatomic, assign) long height;  // 8

@end
Copy the code
SJPerson *sj = [[SJPerson alloc] init];    
NSLog(@"%@ - %lu - %lu - %lu", sj, sizeof(sj), class_getInstanceSize([SJPerson class]), malloc_size((__bridge const void *)(sj)));
Copy the code

Pointer 8 bytes, according to the above structure, we can calculate that the member variable memory alignment takes up 28 -> 32 bytes, plus isa pointer 8 bytes, a total of 40 bytes, SJPerson class takes up 40 bytes, why malloc_size print out 48 bytes, let’s study.

The malloc process

Find the malloc source code to see what the Calloc process is.

  1. _malloc_zone_calloc
void *
calloc(size_t num_items, size_t size)
{
	return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
Copy the code
  1. We find the key information based on the return value PTRzone->calloc

But let’s go in with calloc

void 	*(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */
Copy the code

Calloc = XXX = XXX = XXX = XXX = XXX = XXX We can make a breakpoint on zone->calloc, and when we get to this line of code, on the console Po zone->calloc, we will see the output default_zone_calloc, which we are searching globally. Or with assembly, you can also see the default_zone_calloc method. 3. default_zone_calloc

static void *
default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
	zone = runtime_default_zone();
	
	return zone->calloc(zone, num_items, size);
}
Copy the code

The return value also does not see any information, we interrupt the point repeat, will output nano_calloc 4. Nano_calloc

static void * nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size) { size_t total_bytes; If (calloc_get_size(num_items, size, 0, &total_bytes)) {return null; } if (total_bytes <= NANO_MAX_SIZE) {void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1); if (p) { return p; } else {/* FALLTHROUGH to helper zone */}} // When total_bytes is greater than 256, execute the following code: Malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->calloc(zone, 1, total_bytes); }Copy the code
  1. _nano_malloc_check_clear

Finding the most critical code, segregated_next_block, is an endless loop to find the right memory space.

  1. segregated_next_block

The calLOC flow chart is summarized as follows:

The CalLOC process is basically finished, but what we care most is how much space to apply for? Back in step 5, the slot_bytes field specifies the size of memory. See how this value is obtained online

size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key);
Copy 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 = 16 size = NANO_REGIME_QUANTA_SIZE; // (size + 15) >> 4 << 4, That is, k is the smallest 16-byte alignment data greater than size 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

At this point, that’s why class_getInstanceSize is 40 and malloc_size is 48, because 16-byte alignment is required. So why is it 16 bytes aligned, oc member variables are 8 bytes at most, or why not 32 bytes aligned or whatever. Because if in 8 byte alignment, different object memory space is get together in a row, access error may occur from time to tome, namely wild pointer to access, if extended to 16, the possibility of continuous reduces memory, an NSObject object only one isa pointer, 8 bytes, empty 8 bytes, will reduce the chance of errors, And if you add any member variable, the memory will be larger than 8, and if you align it with 8 bytes, the computation will be larger. Why not use a bigger 32? 32 can be a lot of memory waste, so in summary, iOS object memory space is aligned with 16 bytes.

OC object memory optimization

Print to seesjMemory allocation of

As you can see, the system automatically optimizes memory allocation for us, and the order in which we write attributes is independent of the memory location order.