我们已经知道了alloc中会申请内存空间。如何申请。具体申请多少。如何计算呢? 首先我们应该知道不同类型所占空间
C | OC | 32位 | 64位 |
---|---|---|---|
bool | BOOL | 1 | 1 |
singed char | int8_t,BOOL(32位) | 1 | 1 |
unsigned char | Boolean | 1 | 1 |
short | int 16_t | 1 | 1 |
unsigned short | unchar | 2 | 2 |
int int32_t | NSinterger/boolean_t()32 | 4 | 4 |
unsigned int | NSUintetger | 4 | 4 |
logn | nsinterger(64) | 4 | 8 |
unsigned logn | NSUintetger(64) | 4 | 8 |
long logn | int64_t | 8 | 8 |
float | CGFloat(32) | 4 | 4 |
double | CGFloat(64) | 8 | 8 |
根据内存对齐原则我们可以进行计算。
计算内存的三个方法
sizeof
、class_getInstanceSize
、malloc_size
。
sizeof
是一个运算符,传进来类型,会计算出这个类型占多大内存,在编译器编一阶段就已经确定大小。不能动态分配内存空间大小
class_getInstanceSize
用来获取类的实例对象所占内存大小,返回具体字节数,本质获取对象中成员变量的内存大小。依赖于/#import <objc/runtime.h>
malloc_size
计算对象时间分配的内存大小,系统完成(16字节对齐)。
依赖于#import <malloc/malloc.h>
sizeof(person)
返回的是person的指针。所以是8字节
class_getInstanceSize([LGPerson class])
的内存取决与LGPerson
的成员变量
根据8字节对齐原理总共32
字节。验证结果
如果没有成员变量呢。删除所以成员变量
结果
类的组成经过我们探索发现。类的实例变量最小也会有8字节因为isa
。
class_getInstanceSize
的实现逻辑我们可以通过 objc4-818.2
找到便于你有问题可以查看文章,定位到代码
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
data()->ro()->instanceSize
的大小在编译的时候以及确定了,默认是8 的倍数。有可能不是8 的倍数。就需要word_align(unalignedInstanceSize())
来保证
WORD_MASK = 7
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
8字节对齐算法假设x为8
8 + 7 & ~7
0000 1111 & 1111 1000 = 0000 1000 = 8
class_getInstanceSize
的底层实现可以得到。成员变量8字节对齐的结果加8字节的isa
通过lldb来查看内存数据
x/nfu
有规律打印,iOS为小端,打印结果与X 刚好相反n
要显示的内存单元个数u
表示一个地址单元的长度b
表示单字节h
表示双字节w
表示四字节- ===========
f
表示显示方式可取值如下:x
十六进制显示d
十进制显示u
十进制显示无符号整型o
八进制显示t
二进制显示a
十六进制显示i
指令地址格式c
字符格式显示f
浮点数
我们用lldb来查看一下
”1“的区域为对象首地址。后面为成员变量的值
”2“存的是isa。需要与ISA_MASK
& 运算才正常打印
类方法和对象方法会不会影响呢?
并不会影响。
malloc_size
返回16的倍数
16 + 16 + 16 = 48字节malloc_size
返回48字节
具体是如何开辟内存的
通过libmalloc
源码来探索,我们知道alloc核心是calloc,所以我们测试
void *
calloc(size_t num_items, size_t size)
{
return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
----_malloc_zone_calloc-----
MALLOC_NOINLINE
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;
}
通过源码分析。ptr
应该就是核心了,ptr
的实现是。但是无法直接查看到源码。
最后定位到
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);
}
最终定位到_nano_malloc_check_clear
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;
}
NANO_REGIME_QUANTA_SIZE
宏为16,SHIFT_NANO_QUANTUM
为4、算法解读size + 16 - 1 右移4位,再左移4位,size = 40。40+15=55。
0011 0111
右移 0000 0011
左移 0011 0000
转为10进制48。右移4位,左移四位,16进制对齐。
segregated_next_block
是开辟内存的空间
堆区开辟空间是不连续的,期间可能因多线程,小于最大地址限制,需要while。直至开辟空间成功,返回指针地址
[NSObject alloc]
没用执行 alloc
而是 objc_alloc
。需要通过LLVM来探索。
objc_alloc找到调用的地方
tryGenerateSpecializedMessageSend
负责发生消息,sel
相当于 alloc进行特殊消息处理。
return CGF.EmitObjCAllocWithZone(Receiver,CGF.ConvertType(ResultType));
查看此方法
/// Allocate the given objc object.
/// call i8* \@objc_alloc(i8* %value)
llvm::Value *CodeGenFunction::EmitObjCAlloc(llvm::Value *value,
llvm::Type *resultType) {
return emitObjCValueOperation(*this, value, resultType,
CGM.getObjCEntrypoints().objc_alloc,
"objc_alloc");
}
由此可知,alloc 会去调用obcj_alloc方法。tryGenerateSpecializedMessageSend
这个方法哪里调用,
此代码意思是。runtime在进行消息发生会调用这个方法,判断是不是特殊消息发生,如果是特殊消息发送,
NSObject不走alloc,直接objc_alloc。因为是系统级别的,所以在初始化就会执行。
LLVM对于代码的优化