Memory alignment is a term familiar to programmers, but why? What are the rules for memory alignment?

1.0 What is memory alignment

  • Definition: In modern computers, the memory space is divided by byte. In theory, it seems that access to any type of variable can start at any address, but in practice, access to a specific type of variable is often at a specific memory address.This requires various types of data to be arranged in space according to certain rules, rather than sequential emission one after another, which is alignment.
  • Function: The basic unit of memory is byte, while the CPU accesses and writes 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. CPU access is in blocks, and access to unaligned data may start in one block and end in another. In this way, the middle may be combined together through complex operations, reducing efficiency.Byte alignment improves CPU access speed

2.0 Memory alignment rules

First, we need to know how much memory each data type occupies, as shown in the following table:

Then let’s write a sample demo:

struct LGStruct1 {
    double a;       / / 8 7 [0]
    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 7 [0]
    int b;          // 4 [8 9 10 11]
    char c;         / / 1 [12]
    short d;        // 2 (13 [14 15] 16
}struct2;

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
}struct2;
Copy the code

The following output is displayed:

Struct1 memory:24- struct2 memory:16
Copy the code

Why do two structures with the same data members occupy different amounts of memory? The only difference between the two structures is the order of the data members. Is it related to the order of the data members?

Let’s look at the rules for memory alignment:

  • Data member alignment rulesStruct (or union) data member, the first data member is placed at offset (the position of the beginning address), 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.
  • Structures as membersStruct B (char, int, double, etc.) : If a struct has members, the members are stored at multiples of the size of the largest element inside it.
  • The total sizeof the structure, which is sizeofThe result must be an integer multiple of the largest internal member, and the less must be made up.

Struc1: the size of a double is 8 bytes, and the a member variable will occupy the first 8 bytes, i.e. [0,7]; Char is 1 byte in size, and the b member variable starts at position 8, which is an integer multiple of 1, i.e. [8]; The size of int is 4 bytes, and the c member variable starts at position 9, which is not a multiple of 4, and continues to 12, which is a multiple of 4, [12, 13, 14, 15]. The size of short is 2 bytes, and the d member variable starts at position 16, which is an integer multiple of 2, i.e. [16,17]. Since the total size of the structure is an integer multiple of the largest member, A, which occupies 8 bytes, the size of the struct1 is 24. Note: The starting position starts at 0

So let’s look at what does a structure look like as a member variable according to this rule?

Let’s write a demo and run it:

struct LGStruct1 {
    double a;       / / 8 7 [0]
    char b;         / / 1 [8]
    int c;          // 4 (9 10 11 [12 13 14 15]
    short d;        // 2 [16 17] Total size: 24
}struct1;
struct LGStruct3 {
    double a;                / / 8 7 [0]
    int b;                   / / 4,9,10,11 [8]
    char c;                  / / 1 [12]
    short d;                 / / 2 (13 14 15 []
    int e;                   //4 [16 17 18 19]
    struct LGStruct1 str;    //24 (20 21 22 23 [24....48
}struct3;

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        NSLog(@"Struct1 memory: %lu-struct3 memory: %lu",sizeof(struct1),sizeof(struct3));
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code

The output is as follows:

Struct1 memory:24- struct3 memory:48
Copy the code

Analysis: LGStruct1, as described above, occupies 24 bytes. [0,19] : [0,19] : [0,19] : [0,19] : [0,19] : [0,19] : [0,19] : [0,19] : [0,19] The starting position of STR must be a multiple of 8, that is, 24 bytes from position 24, and the total size of the structure must be a multiple of its largest internal member, that is, it must be a multiple of 24, so the total size is 48.

Memory alignment of 3.0 objects

We have been analyzing structures, but is the memory allocation of objects the same? The essence of an object is a structure.

Let’s start with demo:

@interface GYPerson : NSObject
                                               // isa->8
@property (nonatomic, copy) NSString *name;    / / 8
@property (nonatomic, copy) NSString *nickName;/ / 8
@property (nonatomic, assign) int age;         / / 4
@property (nonatomic, assign) long height;     //8 total size: 40
@end
@implementation GYPerson
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        GYPerson *person = [GYPerson alloc];
        person.name      = @"Next door Lao Wang";
        person.nickName  = @"lw";
        
        NSLog(@"%@ - pointer memory: % lu-gyperson Object memory required: % lu-gyperson actual memory: %lu",person,sizeof(person),class_getInstanceSize([LGPerson class]),malloc_size((__bridge const void(*)person)));
    }
    return 0;
}
Copy the code

The output is as follows:

<GYPerson: 0x10053a600> - Pointer memory:8- Memory required for GYPerson object:40-gyperson Actual memory:48
Copy the code

Person is a pointer, and the pointer takes up 8 bytes. The GYPerson object structure must be an integer multiple of the largest data member 8, i.e., 40. So why is the actual memory allocation at the end 48?

A simple and crude breakpoint debugging analysis source libmalloc source code:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
		void *p = calloc(1.40);
		NSLog(@"%lu",malloc_size(p));
    }
    return 0;
}
Copy the code

Debugging the source code found the following functions called in order:

calloc->_malloc_zone_calloc->default_zone_calloc->_nano_malloc_check_clear->segregated_size_to_fit

The key function segregated_size_to_fit is as follows:

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
	}
	//NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
        // Move the left 4 bits to the right 4 times 16
	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

The source code specifies that the object’s memory allocation is 16 bytes aligned, so the GYPerson object actually allocates 48 memory Spaces instead of 40.

Conclusion:

  • The internal member variables of the structure are aligned with 8 bytes
  • Object’s memory allocation is aligned with 16 bytes