内存对齐规则
1: 数据成员对齐规则: 结构(struct) (或联合(union))的数据成员, 第一个数据成员放在
offset
为0
的地方, 以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员, 比如说是数组, 结构体等)的整数倍
开始(比如int为4字节, 则要从4的整数倍地址开始存储。2: 结构体作为成员: 如果一个结构里有某些结构体成员, 则结构体成员要从其内部最大元素大小的整数倍地址开始存储。 (struct a里存有struct b, b里有char, int, double等元素,那b应该从8的整数倍开始存储。)
3: 结构体的总大小: 也就是
sizeof
的结果, 必须是其内部最大成员的整数倍, 不足的要填充。
Apple源码下载 https://opensource.apple.com/release/macos-10145.html
objc4-756.2
libmalloc
demo
- [1] 计算结构体大小
- [2] oc类的实例大小
- [3] .oc实例内存系统开辟的大小
1. 计算结构体大小
单个结构体:
结构体MyStruct2
struct Struct2 {
double a; // 8
char b; // 1
int c; // 4
short d; // 2
} MyStruct2;
结构体MyStruct2
大小是24
- 先来分析
MyStruct2
double a
长度8
占位0-7
char b
长度1
占位8-8
int c
长度4
占位12-15
(根据规则1
,成员c
从int
大小的整数倍地址开始存储,前面9 10 11
填充3
个字节 。)
short d
长度2
占位16-17
成员大小为18
根据规则3
,结构体MyStruct2
的sizeof
必须是其内部最大成员的整数倍, 不足的要填充。也就是取比18
大的double b 8
的整数倍大小24
,填充24-18 = 6
个字节。
结构体MyStruct3
struct Struct3 {
double a; // 8
int c; // 4
char b; // 1
short d; // 2
} MyStruct3;
结构体MyStruct3
大小是16
- 分析
MyStruct3
,和MyStruct2
不同的是b
和c
的顺序调整了
double a
长度8
占位0-7
int c
长度4
占位8-11
char b
长度1
占位12-12
short d
长度2
占位14-15
(根据规则1
,成员d
从short
大小的整数倍地址开始存储。)
成员大小为16
根据规则3
,结构体MyStruct3
的sizeof
必须是其内部最大成员的整数倍, 不足的要填充。也就是double b 8
的整数倍大小16
,不用填充。
嵌套结构体
struct Struct4 {
NSString *a; // 8
int b; // 4 内存对齐为16
} MyStruct4;
struct Struct5 {
struct MyStruct4 x; // 16
char y; // 1
char t; // 1
CGSize z; // 16
} MyStruct5;
/* Sizes. */
struct CGSize {
CGFloat width;
CGFloat height;
};
typedef struct CG_BOXABLE CGSize CGSize;
结构体MyStruct4
大小是16
,MyStruct5
大小是40
-
分析
MyStruct4
NSString *a
长度8
占位0-7
int b
长度4
占位8-11
,填充4
字节 -
分析
MyStruct5
struct MyStruct4 x
长度16
占位0-15
char y
长度1
占位16-16
char t
长度1
占位17-17
CGSize z
长度16
占位24-39
(根据规则2
,成员CGSize z
需要从从CGSize
内部最大元素大小的整数倍地址开始存储,width
、height
大小都是8
。)
成员大小为40
根据规则3
,结构体MyStruct5
的sizeof
必须是其内部最大成员的整数倍, 不足的要填充。也就是NSString *a 8
的整数倍大小40
,不用填充。
2. oc类的实例大小
oc对象的存储和结构体有些差别,系统会优化属性的存储。
定义一个Person
类:
@interface Person : NSObject
//isa 8
@property (nonatomic) char ch1; // 1
@property (nonatomic, copy) NSString *name; // 8
@property (nonatomic, assign) int age; // 4
@end
@implementation Person
@end
main
方法
打印instanceSize:24 mallocSize:32
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
p.ch1 = 'a';
p.name = @"qwert";
p.age = 13;
Class pCls = [p class];
size_t instanceSize = class_getInstanceSize(pCls);
size_t mallocSize = malloc_size((__bridge const void *)(p));
NSLog(@"instanceSize:%lu mallocSize:%lu", instanceSize, mallocSize);
}
return 0;
}
- 首先分析
alloc
方法,在alloc
处断点,配合lldb
命令s/n
跟到_class_createInstanceFromZone
,有计算实例大小的方法instanceSize
,对齐后不足16
填充到16
个字节。接着调用calloc
申请内存空间。
instanceSize
调用alignedInstanceSize
进行字节对齐
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
assert(isRealized());
uint32_t size = data()->ro->instanceSize;
return size;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
uint32_t size = word_align(unalignedInstanceSize());
return size;
}
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
Person
属性共21
字节:
isa
:8
字节
ch1
:1
字节
name
:8
字节
age
:4
字节
最后会进行字节对齐word_align(x)
,返回大于 x
且是 8
的倍数的最小值:24
。
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
static inline uint32_t word_align(uint32_t x) {
/**
WORD_MASK=7 0000 0111
x=24 0001 1000
x+7 0001 1111
&
~7 1111 1000
-> 0001 1000 = 24
*/
//取一个最小的“比x大的,8的倍数”
return (x + WORD_MASK) & ~WORD_MASK;
//也可以
return (x + WORD_MASK) >> WORD_SHIFT << WORD_SHIFT;
}
word_align
- 回到
main
,分析class_getInstanceSize
方法。
可以看到此方法的调用流程,直接调用字节对齐方法word_align(x)
,所以size_t instanceSize
是输出24
。
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
uint32_t alignedInstanceSize() {
uint32_t size = word_align(unalignedInstanceSize());
return size;
}
image.png
-
main
下一行调用malloc_size(p)
返回的是系统开辟内存的大小,打印:mallocSize:32
extern size_t malloc_size(const void *ptr);
/* Returns size of given ptr */
对象申请的24,系统开辟了32。系统开辟内存的大小,和对象申请的内存大小是不一致的。
- 再次跟到
_class_createInstanceFromZone
,系统调用calloc(1, 24)
分配对象的内存空间
calloc
只能看到声明,源码在开源代码libmalloc
中,下面去libmalloc
工程去分析为什么系统分配的内存大小和对象申请的不一致。
3. oc实例内存系统开辟的大小
- 打开
libmalloc
源码,直接calloc(1, 24)
申请24
的大小。
int main(int argc, const char * argv[]) {
@autoreleasepool {
void *p = calloc(1, 24);
NSLog(@"%lu", malloc_size(p));
}
return 0;
}
打印32
-
lldb
命令跟踪,calloc
方法调用了malloc_zone_calloc
void *
calloc(size_t num_items, size_t size)
{
void *retval;
retval = malloc_zone_calloc(default_zone, num_items, size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}
-
malloc_zone_calloc
方法里面有个zone->calloc(zone, num_items, size)
,这个calloc
和上面的calloc(1, 24)
不是同一个。
- 可以打印出方法名
p zone->calloc
,这个calloc
的实现在default_zone_calloc
这个方法中
-
lldb
命令跟踪到default_zone_calloc
这个方法,里面又有个zone->calloc(zone, num_items, size)
,这个不会是同一个,否则递归调用了。
打印这个方法名,p zone->calloc
,这个calloc
的实现在nano_calloc
这个方法中
-
nano_calloc
方法调用了_nano_malloc_check_clear
,如果返回指针为NULL
,则调用helper_zone
的calloc
- 先来看
_nano_malloc_check_clear
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) {
unsigned debug_flags = nanozone->debug_flags;
#if NANO_FREE_DEQUEUE_DILIGENCE
size_t gotSize;
nano_blk_addr_t p; // the compiler holds this in a register
p.addr = (uint64_t)ptr; // Begin the dissection of ptr
if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
malloc_zone_error(debug_flags, true,
"Invalid signature for pointer %p dequeued from free list\n",
ptr);
}
if (mag_index != p.fields.nano_mag_index) {
malloc_zone_error(debug_flags, true,
"Mismatched magazine for pointer %p dequeued from free list\n",
ptr);
}
gotSize = _nano_vet_and_size_of_free(nanozone, ptr);
if (0 == gotSize) {
malloc_zone_error(debug_flags, true,
"Invalid pointer %p dequeued from free list\n", ptr);
}
if (gotSize != slot_bytes) {
malloc_zone_error(debug_flags, true,
"Mismatched size for pointer %p dequeued from free list\n",
ptr);
}
if (!_nano_block_has_canary_value(nanozone, ptr)) {
malloc_zone_error(debug_flags, true,
"Heap corruption detected, free list canary is damaged for %p\n"
"*** Incorrect guard value: %lu\n", ptr,
((chained_block_t)ptr)->double_free_guard);
}
#if defined(DEBUG)
void *next = (void *)(((chained_block_t)ptr)->next);
if (next) {
p.addr = (uint64_t)next; // Begin the dissection of next
if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
malloc_zone_error(debug_flags, true,
"Invalid next signature for pointer %p dequeued from free "
"list, next = %p\n", ptr, "next");
}
if (mag_index != p.fields.nano_mag_index) {
malloc_zone_error(debug_flags, true,
"Mismatched next magazine for pointer %p dequeued from "
"free list, next = %p\n", ptr, next);
}
gotSize = _nano_vet_and_size_of_free(nanozone, next);
if (0 == gotSize) {
malloc_zone_error(debug_flags, true,
"Invalid next for pointer %p dequeued from free list, "
"next = %p\n", ptr, next);
}
if (gotSize != slot_bytes) {
malloc_zone_error(debug_flags, true,
"Mismatched next size for pointer %p dequeued from free "
"list, next = %p\n", ptr, next);
}
}
#endif /* DEBUG */
#endif /* NANO_FREE_DEQUEUE_DILIGENCE */
((chained_block_t)ptr)->double_free_guard = 0;
((chained_block_t)ptr)->next = NULL; // clear out next pointer to protect free list
} else {
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;
}
返回指针ptr
之前memset(ptr, 0, slot_bytes)
,于是可以跟踪下slot_bytes
是多大,定位到第8
行代码。
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key);
-
segregated_size_to_fit
里面是熟悉的对齐算法,16
字节对齐:返回大于 size = 24 且是 16 的倍数的最小值
:32
。
回到nano_calloc
方法,返回指针p
,一路return
回到main
方法,打印出系统分配的大小malloc_size(p)
为32
.
4. 看源码记录
- 看返回值,找到
始作俑者
- 一些错误处理屏蔽先
- 与
目标
不相关的if else
可以屏蔽
网友评论