foreword
I believe that everyone has shopping experience in convenience stores and supermarkets. When you are shopping, especially when you want to buy a variety of goods, most of you will choose to go to a large supermarket. Convenience store shelves are usually full. There may be snacks, daily necessities, and drinks on the same shelf. When you buy something in the store, you usually call the boss directly. It is very troublesome to find it yourself. The same shelf in a large supermarket usually contains the same type of goods. Even if one shelf is not enough, then put it on another shelf. Shopping efficiency will also be higher.
Usually a byte is the basic unit of memory, but when the CPU operates data, it often accesses it in units of blocks . If there is no set of rules to restrict the storage of memory, some data will be distributed in different blocks, so that the CPU will be very tired and annoying (low efficiency) to find these data. After the bytes are aligned, the CPU does not need to run around to find data (reducing the number of accesses), and the efficiency is relatively improved.
Influencing factors of object memory
Next, analyze the code, first declare a class:
@interface SLPerson : NSObject
@property(strong,nonatomic)NSString * name;
@property(assign,nonatomic)int age;
@end
Study the memory size of the object type, the actual memory size of the object, and the size of the system allocated memory:
SLPerson * p = [SLPerson alloc];
p.name = @"person";
p.age = 18;
SLPerson * p1;
NSLog(@"p对象类型的内存大小--%lu",sizeof(p));
NSLog(@"p对象实际的内存大小--%lu",class_getInstanceSize([p class]));
NSLog(@"p系统分配的内存大小--%lu",malloc_size((__bridge const void *)(p)));
NSLog(@"==================");
NSLog(@"p1对象类型的内存大小--%lu",sizeof(p1));
NSLog(@"p1对象实际的内存大小--%lu",class_getInstanceSize([p1 class]));
NSLog(@"p1系统分配的内存大小--%lu",malloc_size((__bridge const void *)(p1)));
2022-07-01 10:38:26.985044+0800 alloc分析[29679:2671728] p对象类型的内存大小--8
2022-07-01 10:38:26.985110+0800 alloc分析[29679:2671728] p对象实际的内存大小--24
2022-07-01 10:38:26.985138+0800 alloc分析[29679:2671728] p系统分配的内存大小--32
2022-07-01 10:38:26.985164+0800 alloc分析[29679:2671728] ==================
2022-07-01 10:38:26.985186+0800 alloc分析[29679:2671728] p1对象类型的内存大小--8
2022-07-01 10:38:26.985207+0800 alloc分析[29679:2671728] p1对象实际的内存大小--0
2022-07-01 10:38:26.985231+0800 alloc分析[29679:2671728] p1系统分配的内存大小--0
analyze
- Object type memory: p and p1 are essentially pointers, so both sizeof (p) and sizeof (p1) are 8 bytes
- The actual memory of the object: determined by the size of the member variables of the class, name (8), age (4) and isa pointer (8), 8+4+8=20, huh? ? ? No, it clearly outputs 24
- System allocated memory: 32, this. . . . . . Is it getting bigger? Later we will explore malloc for analysis
- As for p1, it is not initialized, so the size occupied by the content of the object is 0
Next, we use LLDB instructions to view the display of object p attributes in memory
We can see that the content of p consists of three 8-bytes. Although age only occupies 4 bytes, the first 4 bytes are also filled with 0, that is, the content size of the object is 8-byte aligned. It also explains why the content of p adds up to 20 bytes, but occupies 24 bytes.
Next we add an instance method and a class method to the class SLPerson
@interface SLPerson : NSObject
@property(strong,nonatomic)NSString * name;
@property(assign,nonatomic)int age;
-(void)test1;
+(void)test2;
@end
same output
2022-07-01 10:41:39.059577+0800 alloc分析[29700:2673212] p对象类型的内存大小--8
2022-07-01 10:41:39.059640+0800 alloc分析[29700:2673212] p对象实际的内存大小--24
2022-07-01 10:41:39.059668+0800 alloc分析[29700:2673212] p系统分配的内存大小--32
2022-07-01 10:41:39.059689+0800 alloc分析[29700:2673212] ==================
2022-07-01 10:41:39.059712+0800 alloc分析[29700:2673212] p1对象类型的内存大小--8
2022-07-01 10:41:39.059733+0800 alloc分析[29700:2673212] p1对象实际的内存大小--0
2022-07-01 10:41:39.059758+0800 alloc分析[29700:2673212] p1系统分配的内存大小--0
Summarize
- Member variables and attributes will affect the memory size of the instance object of the class.
- Adding a method has no effect on the memory size of the instance object of the class , and the method does not exist in the object.
- During the process of adding member variables, because the data types of the member variables are inconsistent, align to the member variable of the largest data type. For classes inherited from NSObject objects, the default byte alignment is 8 bytes .
Structure memory alignment
The essence of an object is actually a structure , and memory alignment can actually be regarded as the memory alignment of the structure. Next, we will explore the memory alignment of the structure.
no nesting
The above results show that the member variables of the two structures are exactly the same, but the order of declaration is different, and the output sizeof is also different.
analyze
Below we use f(x,y) to simulate the storage of member variables, where x represents the initial position of the member variable, and y represents the size of the member variable
SLStruct1:
- a: occupies 8 bytes, a = f(0,8)
- b: occupies 4 bytes, (0+8)%4 = 0, b = f(8,4)
- c: occupies 2 bytes, (8+4)%2 = 0, c = f(12,2)
- d: occupies 1 byte, (12+2)%1 = 0, d = f(14,1)
SLStruct2:
- a: occupies 8 bytes, a = f(0,8)
- d: occupies 1 byte, (0+8)%1 = 0, b = f(8,1)
- b: occupies 4 bytes, (8+1)%4 = 1, needs to move forward 3 positions to reach 12, 12 is divisible by 4, c = f(12,4)
- d: occupies 2 bytes, (12+4)%2 = 0, d = f(16,2)
Summarize
The total memory size of SLStruct1 is 15 bytes, the total memory size of SLStruct2 is 18 bytes, and the maximum memory size of member variables in the two structures is 8 bytes, so the actual memory size of SLStruct1 and SLStruct2 must be an integer multiple of 8 , automatically complete and round up, so the actual size of SLStruct1 is 16 bytes, and the actual size of SLStruct2 is 24 bytes.
nested
Next we create another structure with a structure nested inside it
struct SLStruct3{
long a; // 8
int b; // 4
short c; // 2
char d; // 1
struct SLStruct2 lwStr;
}SLStruct3;
打印结果
2022-07-01 11:24:29.010493+0800 哦哦、[2895:94727] SLStruct1-----16
SLStruct2-----24
SLStruct3-----40
Program ended with exit code: 0
analyze
Similarly, let's analyze SLStruct3
SLStruct1:
- a: occupies 8 bytes, a = f(0,8)
- b: occupies 4 bytes, (0+8)%4 = 0, b = f(8,4)
- c: occupies 2 bytes, (8+4)%2 = 0, c = f(12,2)
- d: occupies 1 byte, (12+2)%1 = 0, d = f(14,1)
- lwStr: The largest member in SLStruct2 occupies 8 bytes, so (14+1)%8 = 7, it needs to be moved forward by 1 position to reach 16, 16 is divisible by 8, c = f(16,18)
Summarize
The combined memory size of SLStruct3 is 34 bytes. The maximum memory size occupied by the member variables a and lwStr in the structure is 8 bytes, so the actual memory size of SLStruct3 must be an integer multiple of 8, which is automatically filled and rounded up. So the actual size of SLStruct3 is 40 bytes.
Structure Alignment Rules
Through the above example analysis, summarize how the structure calculates the size
- The members of struct and union are stored in order. The initial position of the first member is placed at x=0, and the initial position of subsequent members needs to be stored at an integer multiple of the size of the member.
- Assuming that there is a structure member s nested in the member, the initial position of s should be determined according to the size of the largest member in s. For example, the largest member in s occupies 8 bytes, then the initial position of s should be stored in an integer multiple of 8 position.
- After calculating the position of the last member and adding the size of the member (assumed to be r), it depends on whether r is an integer multiple of the largest member in the structure, and if it is insufficient, it must be filled.
- Therefore, the calculation of the content size of the object of the class is also based on this routine. It is nothing more than that the object of the class calculates one more isa (occupying 8 bytes) than the structure at the beginning , and then other members (attributes) are discharged later .
malloc exploration
The malloc_size method is not directly provided. Here I downloaded the libmalloc-317.40.8 library to explore. When exploring alloc before, after passing _class_createInstanceFromZone
the method, calloc is the system to open up memory, so we start with calloc.
1. Call the calloc method
2. Jump to calloc
void *
calloc(size_t num_items, size_t size)
{
return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
3. Jump to _malloc_zone_calloc
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
malloc_zone_options_t mzo)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
void *ptr;
if (malloc_check_start) {
internal_check();
}
ptr = zone->calloc(zone, num_items, size);
if (os_unlikely(malloc_logger)) {
malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
}
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
if (os_unlikely(ptr == NULL)) {
malloc_set_errno_fast(mzo, ENOMEM);
}
return ptr;
}
4. Jump to zone->calloc, find that you can't enter it, and use assembly.
5. Global search 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);
}
6. It is zone->calloc again, continue to assemble
7. Global search 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 */
}
}
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->calloc(zone, 1, total_bytes);
}
8. Enter _nano_malloc_check_clear, at this time we focus on size_t and found a good method segregated_size_to_fit
static void *
_nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{
MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0);
void *ptr;
size_t slot_key;
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
mag_index_t mag_index = nano_mag_index(nanozone);
nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);
ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
if (ptr) {
。。。。。。。
9 Enter segregated_size_to_fit, at this moment, we finally found the core algorithm in the libmalloc source 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;
}
At the same time we see two macro definitions
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
It can be concluded that when we call calloc(1, 20), the final algorithm is through (20 + 16 - 1) >> 4 << 4 operation, the result is 48, that is, the memory alignment is aligned according to 16 bytes. (PS: Shifting one bit to the right is equivalent to dividing by 2, and shifting one bit to the left is equivalent to multiplying by 2)
Summarize
- class_getInstanceSize : Get the memory size occupied by the instance object ( 8 -byte alignment)
- malloc_size : Get the actual memory size allocated by the system ( 16- byte alignment)