IOS underlying exploration of alloc
Let’s explore how the next iOS gets an object by asking a few questions:
- Alloc and init?
- What does the alloc method do?
Alloc versus init
Alloc is used to allocate memory and init is used to initialize data. Let’s verify this with code:
NSObject *obj1 = [NSObject alloc];
NSObject *obj2 = [obj1 init];
NSObject *obj3 = [obj1 init];
NSObject *obj4 = [NSObject alloc];
NSLog(@"obj1: %@, %p, %p", obj1, obj1, &obj1);
NSLog(@"obj2: %@, %p, %p", obj2, obj2, &obj2);
NSLog(@"obj3: %@, %p, %p", obj3, obj3, &obj3);
NSLog(@"obj4: %@, %p, %p", obj4, obj4, &obj4);
obj1: <NSObject: 0x6000000fc580>, 0x6000000fc580, 0x7ffee64db358
obj2: <NSObject: 0x6000000fc580>, 0x6000000fc580, 0x7ffee64db350
obj3: <NSObject: 0x6000000fc580>, 0x6000000fc580, 0x7ffee64db348
obj4: <NSObject: 0x6000000fc6a0>, 0x6000000fc6a0, 0x7ffee64db340
Copy the code
Parsing the print of NSObject:
- Obj1, obj2, obj3 0x6000000FC580, 0x6000000FC6A0, 0x6000000FC6A0
- The pointer addresses of obj1, obj2, obj3, and obj4 variables are all different and sequential, decreasing sequentially because the pointer addresses are allocated on the stack and the stack allocation is continuous.
- Stack and heap memory allocation diagram:
Conclusion:
- Alloc allocates memory addresses, init is used to initialize data.
- Variable pointer addresses are allocated on the stack, and memory addresses are allocated consecutively in strict variable declaration order, from high to low.
- The contents of NSObject objects are usually stored in the heap and allocated from low to high, because heap allocation is to find an address that is available and larger than the size of the memory that needs to be allocated, and it is possible that the later allocated memory address may be smaller.
What does the alloc method do
From my call stack and implementation logic of alloc, the following conclusions can be drawn:
- Allocates the memory required by the object and does memory alignment
- Bind an object and its owning type through the ISA property
The preparatory work
Download the compilable objC4 source code and use it without configuration. If the breakpoint does not take effect, my solution is to move the target -> build Phases -> compile Sources -> file to the front to take effect.
Alloc invocation chain
- NSObject calling alloc
- Call objc_alloc
- callAlloc(cls, true, false)
- NSObject calls +alloc via objc_msgSend
- _objc_rootAlloc
- callAlloc(cls, false, true)
- _objc_rootAllocWithZone
- _class_createInstanceFromZone(): Internally implements memory allocation and binding types
- InstanceSize (): Calculates the memory required by obj and implements memory alignment
- Calloc (): Allocates memory and gets an object
- InitInstanceIsa (): binding type
- Alloc call flow chart:
Allocate memory and implement memory alignment
- The instanceSize() method provides two ways to calculate memory. The first branch goes hasFastInstanceSize() and the second branch goes alignedInstanceSize().
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
Copy the code
2. Check whether the memory size of the instance can be quickly calculated. The __builtin_constant_p() function returns 1 if it is a constant and 0 if it is a variable. And when _class_createInstanceFromZone(CLS, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC) is called extra is passed 0, so the if branch is true, _flags & FAST_CACHE_ALLOC_MASK16 should be called. But in the actual run, it is found to go _flags & FAST_CACHE_ALLOC_MASK. I found this to be true by Po __builtin_constant_p(extra) == 0, I won’t go into the details here because I can’t see the __builtin_constant_p implementation. The final result returns YES, so the next call is fastInstanceSize().
bool hasFastInstanceSize(size_t extra) const
{
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
}
return _flags & FAST_CACHE_ALLOC_MASK;
}
Copy the code
3. Call the fastInstanceSize function, which is where memory alignment is implemented. Since Po __builtin_constant_p(extra) == 0 go to the else branch and call align16() for memory alignment.
size_t fastInstanceSize(size_t extra) const { ASSERT(hasFastInstanceSize(extra)); if (__builtin_constant_p(extra) && extra == 0) { return _flags & FAST_CACHE_ALLOC_MASK16; } else { size_t size = _flags & FAST_CACHE_ALLOC_MASK; // remove the FAST_CACHE_ALLOC_DELTA16 that was added // by setFastInstanceSize return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); }}Copy the code
4. Do (x + size_t(15)) & ~size_t(15) to the desired object in align16(). The purpose is simple: mod 16. When there is a remainder, take this part and add 16. For example, size_t(15) is 01111. If the size_t(15) is greater than 16, the size_t(15) is 10000. 33 binary is 100001, divided by 10000 so you get 100,000 which is 32.
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
Copy the code
IOS allocates memory by alloc and aligns it with 16 bytes. We actually get the ending number of the object either 0 or 8, that’s why.
The else branch of instanceSize() goes to alignedInstanceSize() and word_align().
uint32_t alignedInstanceSize() const { return word_align(unalignedInstanceSize()); } static inline uint32_t word_align(uint32_t x) { return (x + WORD_MASK) & ~WORD_MASK; } # define WORD_MASK 7UL // 64 bits downCopy the code
Summary: Alloc finally calculates the memory required by the object by calling instanceSize() with the _class_createInstanceFromZone() method, is 16 aligned in 64-bit, and then allocates memory via calloc().
Binding type
- Alloc final _class_createInstanceFromZone() method initInstanceIsa() implements type binding.
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); }Copy the code
- Then call the objc_Object ::initIsa() method. On 64-bit machines, ISA is optimized (nonpointer == 1), so go to the else branch and bind obj to Class via setClass()
inline void objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor) { ASSERT(! isTaggedPointer()); isa_t newisa(0); if (! nonpointer) { newisa.setClass(cls, this); } else { ASSERT(! DisableNonpointerIsa); ASSERT(! cls->instancesRequireRawIsa()); newisa.has_cxx_dtor = hasCxxDtor; newisa.setClass(cls, this); newisa.extra_rc = 1; } // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to // guarantee memory order w.r.t. the class index table // ... but not too atomic because we don't want to hurt instantiation isa = newisa; }Copy the code
conclusion
In summary, alloc() implements memory allocation, memory alignment, and object and type binding.
Memory alignment in action
- Apple in 64-bit, object memory alignment is 16, structure is 8.
- Memory allocation is based on the type length of the attribute or member variable, and the starting memory of the attribute or member must be an integer multiple of that type length.
Verify that 64-bit memory alignment is 16
- When allocating memory, the objc-Runtime-new.h _class_createInstanceFromZone() method is finally called
- Call order: _class_createInstanceFromZone() -> instanceSize() -> cache.fastInstancesize () -> align16()
- The final call is to the align16() method, which does memory alignment to the allocated memory X and its rule (x + size_t(15)) & ~size_t(15)
- ~size_t(15): size_t(15) is 01111, or 10000 if larger than 16, followed by 1
- (x + size_t(15)) This is to make sure that you allocate more memory than you actually need, add a 16 up (computers start at 0).
- (x + size_t(15)) & ~size_t(15)
- For example, 13 + 15 = 28, you end up with 16, 28 binary is 11100, &10000 is 10000 is 16
- 18 + 15 = 33 and you get 32, so 33 binary is 100001, and then 10000 is 100,000, which is 32
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
Copy the code
Object memory analysis
@interface LKXObjectDemo1 : NSObject {
// isa // 8
int age; // 4
double hegiht; // 8
char chr; // 1
double weight; // 8
}
@end
@interface LKXObjectDemo2 : NSObject {
// isa // 8
char chr; // 1
int age; // 4
double weight; // 8
double hegiht; // 8
}
@end
@interface LKXObjectDemo3 : NSObject {
@public
// isa // 8
char chr; // 1
int age; // 4
int idx; // 4
double weight; // 8
double hegiht; // 8
}
@end
Copy the code
-
LKXObjectDemo1 Allocates 48 bytes of memory and uses 40 bytes of memory, assuming the start position is 0x10020000
- Isa occupies 8 bytes of memory and starts at 0x10020000 and ends at 0x10020007
- Int age Occupies 4 bytes of memory. The start position is 0x10020008 and the end position is 0x1002000B
- Double hegiht occupies 8 bytes of memory and starts in a multiple of 8, so the start position is 0x10020010 and the end position is 0x10020018
- Char CHR Occupies 1 byte of memory, starts at 0x10020018 and ends at 0x10020018
- The double weight occupies 8 bytes of memory and starts in a multiple of 8, so the start position is 0x10020020 and the end position is 0x10020027
- 0x27 is 40, because the object memory is 16 for it, so 48 memory is allocated
-
LKXObjectDemo2 Allocates 32 bytes of memory and uses 32 bytes of memory, assuming the start position is 0x10020000
- Isa occupies 8 bytes of memory and starts at 0x10020000 and ends at 0x10020007
- Char CHR Occupies 1 byte of memory, starts at 0x10020008 and ends at 0x10020008
- Int age Occupies 4 bytes of memory. The start position is 0x1002000B and the end position is 0x1002000F
- The double weight occupies 8 bytes of memory and starts at 0x10020010 and ends at 0x10020017
- The double hegiht occupies 8 bytes of memory and starts at 0x10020018 and ends at 0x1002001F
- 0x1F is 32, so it takes up 32 bytes
-
LKXObjectDemo3 Allocates 48 bytes of memory and uses 40 bytes of memory, assuming the start position is 0x10020000
- Isa occupies 8 bytes of memory and starts at 0x10020000 and ends at 0x10020007
- Char CHR Occupies 1 byte of memory, starts at 0x10020008 and ends at 0x10020008
- Int age Occupies 4 bytes of memory. The start position is 0x1002000B and the end position is 0x1002000F
- Int Memory occupied by IDX 4 bytes. The start position is 0x10020010 and the end position is 0x10020013
- The double weight occupies 8 bytes of memory. The start position is 0x10020018 and the end position is 0x1002001F
- The double hegiht occupies 8 bytes of memory and starts at 0x10020020 and ends at 0x10020027
- 0x27 is 40, because the object memory is 16 for it, so 48 memory is allocated
-
Demo3 member variable analysis, as can be seen from the output
- The memory address of demo3(0x101B0B840) is 8 bytes different from that of CHR (0x101B0B848). This 8 bytes is the address of ISA. The memory of demo3 is 0x011D8001000085F9. The memory address of [LKXObjectDemo3 class] is 0x00000001000085F8, which is exactly the same as the last 9 bits, indicating that ISA points to the memory address of the class type
- CHR (0x101b0B848) and CHR2 (0x101b0B849) are 1 byte apart and point to memory 0x0000000a00003363
- The memory addresses from CHR (0x101B0B848), AGE (0x101B0B84c), and IDX (0x101B0B850) are adjacent and 4 bytes apart, indicating that the memory allocated by the member attribute must be an integer multiple of its type length, because the int type length is 4. Since the char type is 1 in length, it doesn’t matter.
- Weight (0x101C042C8) and height(0x101C042d0) are 8 bytes each
demo3->chr = 'c';
demo3->age = 10;
demo3->idx = 1;
demo3->weight = 120;
demo3->hegiht = 170;
NSLog(@"chr: %p, age: %p, idx: %p, weight: %p, height: %p", &(demo3->chr), &(demo3->age), &(demo3->idx),
&(demo3->weight), &(demo3->hegiht));
demo3: 0x101b0b840
chr: 0x101b0b848, chr2: 0x101b0b849,
age: 0x101b0b84c, idx: 0x101b0b850,
weight: 0x101b0b858, height: 0x101b0b860
0x101b0b840: 0x011d8001000085f9 0x0000000a00003363
0x101b0b850: 0x0000000000000001 0x405e000000000000
0x101b0b860: 0x4065400000000000 0x0000000000000000
0x101b0b870: 0x0000000000000000 0x0000000000000000
p [LKXObjectDemo3 class]
(Class) $1 = 0x00000001000085f8
Copy the code
Struct memory analysis
struct StructDemo1 {
char ch; // 1
double height; // 8
float weight; // 4
char *name; // 8
int age; // 4
} StructDemo1;
struct StructDemo2 {
char ch; // 1
int age; // 4
char *name; // 8
double height; // 8
float weight; // 8
} StructDemo2;
struct StructDemo3 {
struct StructDemo1 s1; // 40
struct StructDemo2 s2; // 32
float weight; // 4
char chr; // 1
int index; // 4
double height; // 8
} StructDemo3;
Copy the code
-
StructDemo1 memory is 40 bytes in size, because each attribute must be of type length, if the starting position is 0x10020000
- Char ch takes up 1 byte, so ch starts at 0x10020000 and ends at 0x10020000
- Double height takes 8 bytes and starts at 0x10020008 and ends at 0x1002000F if it is a multiple of 8
- Float weight takes 4 bytes and starts at 0x10020010 and ends at 0x10020014
- Char *name occupies 8 bytes. Name starts at 0x10020018 and ends at 0x1002001F
- Int age takes 4 bytes. Age starts at 0x10020020 and ends at 0x10020023
- 0x23 is 36, and since the struct memory is 8 bytes for it, 40 bytes are finally allocated
-
StructDemo2 memory size is 32 bytes, if the start position is 0x10020000
- Char ch takes up 1 byte, so ch starts at 0x10020000 and ends at 0x10020000
- Int age takes 4 bytes and starts at 0x10020004 and ends at 0x10020007
- Char *name occupies 8 bytes. Name starts at 0x10020008 and ends at 0x1002000F
- Double height takes 8 bytes, so height starts at 0x10020010 and ends at 0x10020017
- Float weight takes 4 bytes and starts at 0x10020018 and ends at 0x1002001B
- 0x1B is 28, because struct memory is 8 bytes for it, so 32 bytes are finally allocated
-
StructDemo1 memory size is 96 bytes, if the starting position is 0x10020000
- Struct StructDemo1 s1 occupies 40 bytes. S1 starts at 0x10020000 and ends at 0x10020027
- Struct StructDemo2 S2 occupies 32 bytes. S1 starts at 0x10020028 and ends at 0x10020047
- Float weight takes 4 bytes and starts at 0x10020048 and ends at 0x1002004B
- Char CHR occupies 1 byte, so the CHR starts at 0x1002004C and ends at 0x1002004C
- Int index takes 4 bytes and starts at 0x10020050 and ends at 0x10020053
- Double height takes 8 bytes, so height starts at 0x10020058 and ends at 0x1002005F
- 0x5F is 96, which uses exactly 96 bytes
supplement
Why memory alignment?
- Platform migration issues: Different hardware platforms have their own rules for accessing addresses, and not all hardware can access all locations at will.
- Performance issues: Data structures (especially stacks) should be aligned on natural boundaries whenever possible. To access unaligned memory, the processor needs to make two memory accesses; Aligned memory access is only required once.
Refer to the article
OC basic principle of the object of the nature (a) alloc exploration of OC basic principle of the exploration of alloc