美文网首页内存
(二) 内存对齐

(二) 内存对齐

作者: 喵喵粉 | 来源:发表于2019-12-20 17:48 被阅读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] 计算结构体大小
    • [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,成员cint大小的整数倍地址开始存储,前面9 10 11填充3个字节 。)
      short d 长度2 占位16-17
      成员大小为18
    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,和MyStruct2不同的是bc的顺序调整了
      double a 长度8 占位0-7
      int c 长度4 占位8-11
      char b 长度1 占位12-12
      short d 长度2 占位14-15 (根据规则1,成员dshort大小的整数倍地址开始存储。)
      成员大小为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

    • 分析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内部最大元素大小的整数倍地址开始存储,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;
    }
    
    • 首先分析alloc方法,在alloc处断点,配合lldb命令s/n 跟到_class_createInstanceFromZone,有计算实例大小的方法instanceSize,对齐后不足16填充到16个字节。接着调用calloc申请内存空间。
    _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
    • 回到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)分配对象的内存空间
    _class_createInstanceFromZone

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

    calloc

    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)不是同一个。
    malloc_zone_calloc
    • 可以打印出方法名 p zone->calloc,这个calloc的实现在default_zone_calloc这个方法中
    malloc_zone_calloc
    • lldb命令跟踪到default_zone_calloc这个方法,里面又有个zone->calloc(zone, num_items, size),这个不会是同一个,否则递归调用了。

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

    default_zone_calloc
    • nano_calloc方法调用了_nano_malloc_check_clear,如果返回指针为NULL,则调用helper_zonecalloc
    nano_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
    segregated_size_to_fit

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

    main

    4. 看源码记录

    • 看返回值,找到始作俑者
    • 一些错误处理屏蔽先
    • 目标不相关的if else可以屏蔽

    相关文章

      网友评论

        本文标题:(二) 内存对齐

        本文链接:https://www.haomeiwen.com/subject/qewbnctx.html