内存

(二) 内存对齐

2019-12-20  本文已影响0人  喵喵粉
内存对齐规则

1: 数据成员对齐规则: 结构(struct) (或联合(union))的数据成员, 第一个数据成员放在offset0的地方, 以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员, 比如说是数组, 结构体等)的整数倍开始(比如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. 计算结构体大小

单个结构体:

结构体MyStruct2

struct Struct2 {
    double a;   // 8
    char b;     // 1
    int c;      // 4
    short d;    // 2
} MyStruct2;  

结构体MyStruct2大小是24

MyStruct2

根据规则3,结构体MyStruct2sizeof必须是其内部最大成员的整数倍, 不足的要填充。也就是取比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

根据规则3,结构体MyStruct3sizeof必须是其内部最大成员的整数倍, 不足的要填充。也就是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大小是16MyStruct5大小是40

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内部最大元素大小的整数倍地址开始存储,widthheight大小都是8。)
成员大小为40

MyStruct5

根据规则3,结构体MyStruct5sizeof必须是其内部最大成员的整数倍, 不足的要填充。也就是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;
}
_class_createInstanceFromZone

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字节:
isa8字节
ch11字节
name8字节
age4字节

最后会进行字节对齐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
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
extern size_t malloc_size(const void *ptr);
    /* Returns size of given ptr */

对象申请的24,系统开辟了32。系统开辟内存的大小,和对象申请的内存大小是不一致的。

_class_createInstanceFromZone

calloc只能看到声明,源码在开源代码libmalloc中,下面去libmalloc工程去分析为什么系统分配的内存大小和对象申请的不一致。

calloc

3. oc实例内存系统开辟的大小

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void *p = calloc(1, 24);
        NSLog(@"%lu", malloc_size(p));
    }
    return 0;
}

打印32

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 malloc_zone_calloc

打印这个方法名,p zone->calloc,这个calloc的实现在nano_calloc这个方法中

default_zone_calloc nano_calloc
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

回到nano_calloc方法,返回指针p,一路return回到main方法,打印出系统分配的大小malloc_size(p)32.

main

4. 看源码记录

上一篇下一篇

猜你喜欢

热点阅读