1. calloc

In the last section, we learned that we use the function calloc to create space, so let’s go back to the internal logic of this function.

However, it was found that the calloc method could not continue the follow-up, as shown in the figure:

We found that calloc is defined in the malloc/_malloc.h file, and we found the corresponding source. I’m using a working source code here.

Next, go directly to the code, in the main function:

Void *p = calloc(1, 40); void *p = calloc(1, 40); NSLog(@"%lu",malloc_size(p));Copy the code

Run it directly to see the internal implementation of malloc.c -> calloc.

void *
calloc(size_t num_items, size_t size)
{
	return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
Copy the code

As you can see, calloc just calls the _malloc_zone_calloc method and does nothing else. Note that default_zone, which is a virtual default zone, can be used to do something like this:

1.1 _malloc_zone_calloc

static void *
_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;
}
Copy the code

Calloc calls _malloc_zone_calloc directly. Let’s look at the implementation of this function. By returning the value, we determine that the focus code should be related to the PTR. So _malloc_zone_calloc is condensed as follows:

static void * _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; PTR = zone->calloc(zone, num_items, size); return ptr; }Copy the code

Zone ->calloc (zone->calloc); This is, we use LLDB debugging, print the data:

(lldb) p zone->calloc
(void *(*)(_malloc_zone_t *, size_t, size_t)) $0 = 0x00000001002f4b93
(.dylib`default_zone_calloc at malloc.c:385)
Copy the code

Sensation discovers a new continent: default_zone_calloc at line 385 of the malloc.c file.

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

Over here, we have another zone. This is the default zone for creating the Runtime.

MALLOC_NOEXPORT malloc_zone_t* lite_zone = NULL; MALLOC_ALWAYS_INLINE static inline malloc_zone_t * runtime_default_zone() {// lite_zone = null, So create return (lite_zone)? lite_zone : inline_malloc_default_zone(); } ⬇️ static inline malloc_zone_t * inline_malloc_default_zone(void) {os_ONCE _malloc_initialize_once(); // malloc_report(ASL_LEVEL_INFO, "In inline_malloc_default_zone with %d %d\n", malloc_num_zones, malloc_has_debug_zone); // malloc_zones = (malloc_zone_t **)0xdeaddeaddeaddead; return malloc_zones[0]; }Copy the code

Here’s a look at malloc_zones:

1.2 _malloc_zone_t

Most of the above is around _malloc_zone_t.

The malloc_zones[0] element is a malloc_zone_t * element. The malloc_zone_t element is a malloc_zone_t * element. The malloc_zone_t element is a malloc_zone_t * element.

Take a look at the _malloc_zone_t structure

#define MALLOC_ZONE_FN_PTR(fn) fn typedef struct _malloc_zone_t { /* Only zone implementors should depend on the layout of this structure; Regular callers should use the access functions below */ void *reserved1; /* RESERVED FOR CFAllocator DO NOT USE */ void *reserved2; /* RESERVED FOR CFAllocator DO NOT USE */ size_t (* MALLOC_ZONE_FN_PTR(size))(struct _malloc_zone_t *zone, const void *ptr); void *(* MALLOC_ZONE_FN_PTR(malloc))(struct _malloc_zone_t *zone, size_t size); void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); void *(* MALLOC_ZONE_FN_PTR(valloc))(struct _malloc_zone_t *zone, size_t size); /* same as malloc, but block returned is set to zero and is guaranteed to be page aligned */ void (* MALLOC_ZONE_FN_PTR(free))(struct _malloc_zone_t *zone, void *ptr); void *(* MALLOC_ZONE_FN_PTR(realloc))(struct _malloc_zone_t *zone, void *ptr, size_t size); void (* MALLOC_ZONE_FN_PTR(destroy))(struct _malloc_zone_t *zone); const char *zone_name; . boolean_t (* MALLOC_ZONE_FN_PTR(claimed_address))(struct _malloc_zone_t *zone, void *ptr); } malloc_zone_t;Copy the code

The data in the structure is the same as the content in our image, defining many methods. Take Calloc for example.

void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); Calloc (_malloc_zone_t *zone, size_tnum_items, size_t size) {// call the method in dylib, nano_malloc.c: 878 nano_calloc(zone, num_items, size); }Copy the code

We can also print it in the same way:

(lldb) po zone->calloc
(.dylib`nano_calloc at nano_malloc.c:878)
Copy the code

The printout is complete, and also tells us that the nano_calloc method is in the nano_malloc.c file.

1.3 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 the size obtained is within the maximum range specified by the system, malloc is performed directly. 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); // If initialization fails, continue execution. return zone->calloc(zone, 1, total_bytes); }Copy the code

In nano_calloc, the return value is the same as that of zone->calloc. And the run does go here.

Here’s the point

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; // Slot key // get the true memory size, and slot_key size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); 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) {if (PTR) {if (PTR) {} else { Return PTR = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index); } if (cleared_requested && ptr) { memset(ptr, 0, slot_bytes); // TODO: Needs a memory barrier after memset to ensure zeroes land first? } return ptr; }Copy the code

Let’s look at the algorithm that creates space: segregated_size_to_fit

1.4 segregated_size_to_fit

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

The algorithm calculated in the previous section is similar to alignedInstanceSize. The main logic here is to move four to the right, then four to the left. It’s like wiping out the last four digits. Let’s take the 40 we’ve opened up the space to pass in:

(size + nano_regime_quanta_size-1) = 40 + 15-1 54 -> 0011 0110 >> 4 0000 0011 // right 4 bit << 4 0011 0000 = 48 // left 4 bitCopy the code

So this is going to be 48. While slot_key = 47. Then we get the memory pointer.

This is calloc’s flow and, again, iOS is 16 byte aligned in memory.

2. Memory alignment of the structure

Structure alignment 3 dozen principles:

  1. Data member alignment rules: The first data member of a struct is stored at offset 0, and the starting position of each data member is the size of the member or 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 struct members are stored from an integer multiple of the size of the largest element in the structure. (struct A contains struct B, char, int, double, etc.)
  3. Finishing off: 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.

Before we start coding, let’s know how much memory each data type takes up:

Let’s look at the structure’s memory:

struct Struct1 {
    double a;   
    char b;     
    int c;      
    short d;    
}str1;

struct Struct2 {
    double a; 
    int b;    
    char c;   
    short d;  
}str2;

NSLog(@"str1 = %lu, str2-%lu",sizeof(str1),sizeof(str2));
Copy the code

The output is pretty clear. str1 = 24, str2-16

Let’s analyze:

struct Struct1 { double a; // 8 (0-7) char b; // 1 [8 1] (8) int c; // 4 [9 10 11 12] 9 is not a multiple of 4 (12 13 14 15). // 2 [16 17] }str1; // struct integer times: 24 struct Struct2 {double a; //8 (0-7) int b; //4 (8 9 10 11) char c; //1 (12) short d; }str2;}str2;}str2; NSLog(@"str1 = %lu, str2-%lu",sizeof(str1),sizeof(str2));Copy the code

What if the structure uses the structure inside?

struct Struct3 {
    double a;   //8 (0-7)
    int b;      //4 (8 9 10 11)
    char c;     //1 (12)
    struct Struct2 str_2;   // 13 14 15 (16 - 31)
}str3;

NSLog(@"str3 = %lu", sizeof(str3));
Copy the code

Following the second principle of memory alignment, a structure member starts as an integer multiple of the size of its largest internal member. C is 12, and the next position is 13, which is not an integer multiple of 8. So, as a matter of principle, I’m going to start at 16. So the size of the STR3 structure is 32.

3. Align the memory of objects

As usual, let’s look at the code:

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int score;
@property (nonatomic, assign) long height;
@property (nonatomic) char c1;
@property (nonatomic) char c2;

@end

main() {

Person *person = [Person alloc];
person.name = @"name";
person.age = 18;
person.score = 20;
person.height = 180;
person.c1 = 'a';
person.c2 = 'b';

NSLog(@"%@ - %lu - %lu - %lu",person,sizeof(person),class_getInstanceSize([Person class]),malloc_size((__bridge const void *)(person)));
        
}
Copy the code

Let’s make a breakpoint on the NSLog. If you have time, you can comment out the attributes, starting at 0, and add them up one by one to see what the output is.

  1. Po directly prints the memory address of the object that person points to.

    (lldb) po person
    <Person: 0x600002adbcf0>
    Copy the code
  2. There is a point that needs to be mentioned. The x person command was used in the previous chapter, and the output is the same as that displayed in View Memory. And x over 8gx is just sorting. 8 stands for output of 8 groups of memory. If it’s 4, it prints 4 sets of content. Each one is 8 bytes.

    (lldb) x/8gx person
    0x600002adbcf0: 0x0000000103930808 0x0000001200006261
    0x600002adbd00: 0x0000000000000014 0x000000010392b038
    0x600002adbd10: 0x00000000000000b4 0x0000000000000000
    0x600002adbd20: 0x0000c1c5c19bbd20 0x00000000000007fb
    Copy the code

    0x600002ADBcf0: is the first address pointed to by Person. And then you store the values of the properties. Memory is continuous.

  3. Let’s output the contents of memory separately.

    (LLDB) Po 0x0000000103930808 Person // ISA pointer (LLDB) Po 0x00000012 18 (LLDB) 0x61 is decimal 97 -> a (LLDB) Po 0x0000000000000014 20 (lldb) po 0x000000010392b038 name (lldb) po 0x00000000000000b4 180Copy the code

    0x0000001200006261: This block of address is unpacked. We know that int is 4 bytes and char is 1 byte, so the first bits are int values and the second bits are split.

    Apple did enough in the memory is optimized, although is aligned to 16 bytes in memory, in space, in time, improve the efficiency of reading, but on the internal implementation, or a lot of optimization, the aim is to maximize saving memory at the same time guarantee the security of data, but it is important to note that attributes are aligned at 8 bytes.

    But why aren’t two ints put together? If you comment out all the char types, the two ints will be stored together. Char data will be filled in first. Also, try more, what about 3 char, etc…

  4. The final output is

    <Person: 0x600002adbcf0> - 8 - 40 - 48
    Copy the code

    Let’s analyze the output:

    • Person: The current class object, the pointer to it, and the memory address to which it points.
    • Sizeof (person) : The person holds a pointer, 8 bytes.
    • Class_getInstanceSize ([Person class]) : The space this class really needs. Properties are 8-byte aligned.
    • Malloc_size ((__bridge const void *)(person)) : The space that needs to be opened in memory. The memory space is 16 bytes aligned.

3.1 float and double

What if we change one of the attributes bit double? I’m going to change the height here to be a double. Take a look at the output

(lldb) x/8gx person
0x600000045530: 0x000000010626d808 0x0000001200006261
0x600000045540: 0x0000000000000014 0x0000000106268038
0x600000045550: 0x4066800000000000 0x0000000000000000
0x600000045560: 0x0000000000000000 0x0000000000000000
(lldb) po 0x4066800000000000
4640537203540230144
Copy the code

How come there is no output 180? This is because the system performs a special conversion for float and double data. There is no way to read a double directly from memory.

But we can convert a double to see if there is a value above the bit.

(lldb) p/x (double)180.0
(double) $4 = 0x4066800000000000
Copy the code

After the transformation, it turns out to be exactly the corresponding data.

There is of course a method for reading floating point numbers p/f [0x000000123]

(lldb) p/f 0x4066800000000000
(long) $5 = 180
Copy the code

That’s what memory completion is all about

4. Conclusion:

  • Calloc process.
  • Alignment rules for structures.
  • Class object properties are 8-byte aligned, but 16-byte aligned in memory space.
  • The use of x / 4 gx

reference

Libmalloc source

More apple source code