redis6.2源碼解析-內(nèi)存管理

redis 的內(nèi)存管理主要由zmalloc.h和zmalloc.c來實(shí)現(xiàn)的, 它主要的作用是提供統(tǒng)一的內(nèi)存管理方法, 屏蔽底層不同系統(tǒng)不同分配器的差異.

1 C語言相關(guān)基礎(chǔ)

  • 宏定義中#s 表示將s符號變成字符串
  • 宏定義中 a##b 用于鏈接兩個(gè)符號變成一個(gè)
  • *(&a)=2 相當(dāng)于給指針a的變量賦值為2, * 不止可以讀取指針變量的值, 還可以賦值
  • sdtlib.h/abort(void) 中止程序執(zhí)行,直接從調(diào)用的地方跳出
  • fprintf() 發(fā)送格式化輸出到流 stream 中
  • fflush() 刷新輸出流

2 分配器的介紹

  • tcmalloc: 由google用于優(yōu)化C++多線程就應(yīng)用而開發(fā)
  • jemalloc: 一個(gè)通用的malloc(3)實(shí)現(xiàn), 著重于減少內(nèi)存碎片和提高并發(fā)性能
  • 蘋果系統(tǒng)自帶的 malloc
  • 其他系統(tǒng)或glibc
  • 標(biāo)準(zhǔn)的libc

3 關(guān)于內(nèi)存對齊

這里我引用別人文章的一段話來介紹一下內(nèi)存對齊的概念

CPU一次性能讀取數(shù)據(jù)的二進(jìn)制位數(shù)稱為字長,也就是我們通常所說的32位系統(tǒng)(字長4個(gè)字節(jié))、64位系統(tǒng)(字長8個(gè)字節(jié))的由來。
所謂的8字節(jié)對齊,就是指變量的起始地址是8的倍數(shù)。
比如程序運(yùn)行時(shí)(CPU)在讀取long型數(shù)據(jù)的時(shí)候,只需要一個(gè)總線周期,時(shí)間更短,
如果不是8字節(jié)對齊的則需要兩個(gè)總線周期才能讀完數(shù)據(jù)。
本文中我提到的8字節(jié)對齊是針對64位系統(tǒng)而言的,如果是32位系統(tǒng)那么就是4字節(jié)對齊。
實(shí)際上Redis源碼中的字節(jié)對齊是軟編碼,而非硬編碼。
里面多用sizeof(long)或sizeof(size_t)來表示。
size_t(gcc中其值為long unsigned int)和long的長度是一樣的,
long的長度就是計(jì)算機(jī)的字長。
這樣在未來的系統(tǒng)中如果字長(long的大?。┎皇?個(gè)字節(jié)了,該段代碼依然能保證相應(yīng)代碼可用。

redis3.0的時(shí)候, 在內(nèi)存統(tǒng)計(jì)和自定義實(shí)現(xiàn)malloc_size時(shí)都會進(jìn)行手動(dòng)內(nèi)存對齊, redis 6.0 的版本只在進(jìn)行統(tǒng)計(jì)的內(nèi)存時(shí), 會嘗試進(jìn)行手動(dòng)內(nèi)存對齊的, 但是redis 6.2后, 把所有手動(dòng)內(nèi)存對齊的代碼刪除了, 這里還沒找到原因. 為什么越后面的版本越不需要手動(dòng)內(nèi)存對齊?

4 內(nèi)存模型

image.png
image.png

首部: PREFIX_SIZE
目標(biāo)分配內(nèi)存大小: 也就是入?yún)⒌腟IZE
填充字節(jié)數(shù)(對齊)是由malloc自動(dòng)完成的

image.png
image.png

redis 分配的內(nèi)存主要由三部分組成, 分別是首部PREFIX_SIZE, 目標(biāo)內(nèi)存SIZE, 和對齊填充字節(jié), 底層malloc返回的是 realptr 指針, redis 上層使用的指向數(shù)據(jù)的指針ptr.

5 zmalloc.h源碼解析

zmalloc.h的主要邏輯是聲明一些通用的方法, 并且根據(jù)底層不同分配器的實(shí)現(xiàn), 申明相關(guān)宏定義參數(shù), 如: HAVE_MALLOC_SIZE, HAVE_DEFRAG
redis6.2可選的分配器有 tcmalloc/jemalloc/apple malloc/存在 malloc_usable_size方法的 libc/原生的libc, 按我看的理解, 最終都不滿足的話, 最后應(yīng)該用ANSI libc 來處理, 并且需要手動(dòng)實(shí)現(xiàn)zmalloc_size()方法和zmalloc_usable_size()方法.


//一般C文件都是在聲明一個(gè)文件標(biāo)識, 用于避免文件重復(fù)引用
#ifndef __ZMALLOC_H
#define __ZMALLOC_H

/* Double expansion needed for stringification of macro values. */
#define __xstr(s) __str(s)
//將s變成字符串
#define __str(s) #s
//分別判斷使用tcmalloc庫/jemalloc庫/蘋果庫哪個(gè)作為底層的malloc函數(shù)調(diào)用
#if defined(USE_TCMALLOC)
//拼接 ZMALLOC_LIB 字符串
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
//引入庫
#include <google/tcmalloc.h>
//限定使用的版本號
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
//定義 HAVE_MALLOC_SIZE
#define HAVE_MALLOC_SIZE 1
//定義獲取指針對應(yīng)的內(nèi)存大小
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif

#elif defined(USE_JEMALLOC)
//拼接ZMALLOC_LIB字符串
#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
//引 jemalloc 的庫
#include <jemalloc/jemalloc.h>
//限定版本號
#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) je_malloc_usable_size(p)
#else
#error "Newer version of jemalloc required"
#endif

//mac的庫
#elif defined(__APPLE__)
#include <malloc/malloc.h>
//是否存在獲取已分配內(nèi)存大小的方法
#define HAVE_MALLOC_SIZE 1
//獲取指針對象分配內(nèi)存的大小, 為什么需要這個(gè)方法呢, zmalloc_size 會返回不包括內(nèi)存大小頭(PREFIX_SIZE)的內(nèi)存大小
#define zmalloc_size(p) malloc_size(p)
#endif

/* On native libc implementations, we should still do our best to provide a
 * HAVE_MALLOC_SIZE capability. This can be set explicitly as well:
 *
 * NO_MALLOC_USABLE_SIZE disables it on all platforms, even if they are
 *      known to support it.
 * USE_MALLOC_USABLE_SIZE forces use of malloc_usable_size() regardless
 *      of platform.
 */
//沒有聲明內(nèi)存分配的庫
#ifndef ZMALLOC_LIB
//定義ZMALLOC_LIB為"libc"
#define ZMALLOC_LIB "libc"
//是 glibc 或者 freeBSD系統(tǒng) 或 如果存在 malloc_usable_size() 方法
//malloc_usable_size 函數(shù)中傳入一個(gè)指針,返回指針指向的空間實(shí)際占用的大小,
//這個(gè)返回的大小,可能會比使用malloc申請的要大,由于系統(tǒng)的內(nèi)存對齊或者最小分配限制
#if !defined(NO_MALLOC_USABLE_SIZE) && \
    (defined(__GLIBC__) || defined(__FreeBSD__) || \
     defined(USE_MALLOC_USABLE_SIZE))
#include <malloc.h>
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_usable_size(p)
#endif
#endif

/* We can enable the Redis defrag capabilities only if we are using Jemalloc
 * and the version used is our special version modified for Redis having
 * the ability to return per-allocation fragmentation hints. */
#if defined(USE_JEMALLOC) && defined(JEMALLOC_FRAG_HINT)
//定義是否支持內(nèi)存碎片整理
#define HAVE_DEFRAG
#endif

//申請大小為size的內(nèi)存空間, 不進(jìn)行初始化, 有可能有臟數(shù)據(jù)
void *zmalloc(size_t size);
//以塊的形式申請內(nèi)存, 默認(rèn)是1塊, 對應(yīng) calloc, 并初始化為0
void *zcalloc(size_t size);
//重新調(diào)用已申請的內(nèi)存大小為size
void *zrealloc(void *ptr, size_t size);
//嘗試用 malloc 申請內(nèi)存
void *ztrymalloc(size_t size);
//嘗試用 calloc 申請內(nèi)存
void *ztrycalloc(size_t size);
void *ztryrealloc(void *ptr, size_t size);
//釋放內(nèi)存
void zfree(void *ptr);
void *zmalloc_usable(size_t size, size_t *usable);
void *zcalloc_usable(size_t size, size_t *usable);
void *zrealloc_usable(void *ptr, size_t size, size_t *usable);
void *ztrymalloc_usable(size_t size, size_t *usable);
void *ztrycalloc_usable(size_t size, size_t *usable);
void *ztryrealloc_usable(void *ptr, size_t size, size_t *usable);
void zfree_usable(void *ptr, size_t *usable);
//字符串復(fù)制
char *zstrdup(const char *s);
//獲取redis已經(jīng)使用(分配)的內(nèi)存大小
size_t zmalloc_used_memory(void);
//自定義內(nèi)存溢出時(shí)回調(diào)函數(shù)
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
//獲取RSS(常駐內(nèi)存集)大小
size_t zmalloc_get_rss(void);
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
void set_jemalloc_bg_thread(int enable);
int jemalloc_purge();
//獲取進(jìn)程私有的內(nèi)容已經(jīng)發(fā)生更改的內(nèi)存大小
size_t zmalloc_get_private_dirty(long pid);
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
//獲取物理內(nèi)存大小
size_t zmalloc_get_memory_size(void);
//直接調(diào)用系統(tǒng)free函數(shù)釋放已分配的內(nèi)存
void zlibc_free(void *ptr);

//如果開啟內(nèi)存碎片整理
#ifdef HAVE_DEFRAG
void zfree_no_tcache(void *ptr);
void *zmalloc_no_tcache(size_t size);
#endif

//沒有獲取已分配內(nèi)存大小的方法, 則聲明兩個(gè)函數(shù), 給 zmalloc.c 進(jìn)行手動(dòng)實(shí)現(xiàn), 這里有點(diǎn)像java的抽象方法
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr);
size_t zmalloc_usable_size(void *ptr);
#else
//將 zmalloc_size 方法重定義為 zmalloc_usable_size, 用于獲取指針對象大小
#define zmalloc_usable_size(p) zmalloc_size(p)
#endif

#ifdef REDIS_TEST
int zmalloc_test(int argc, char **argv);
#endif

#endif /* __ZMALLOC_H */

由上述方法聲明可以我們可以猜到, redis內(nèi)存分配方法分兩種

  • 直接申請, 申請不了則報(bào)內(nèi)存溢出, 如: zmalloc(), zcalloc(), zrealloc()
  • 嘗試申請, 申請不了則返回NULL, 如: ztrymalloc(), ztrycalloc(), ztryrealloc()

6 zmalloc.c 的源碼解析

zmalloc.c主要是對zmalloc.h的函數(shù)聲明的實(shí)現(xiàn), zmalloc.h有點(diǎn)像java的接口類.

redis分配內(nèi)存時(shí), 會根據(jù)宏定義變量 HAVE_MALLOC_SIZE 是否存在來處理 PREFIX_SIZE 的值, 如果 HAVE_MALLOC_SIZE 存在, 則可以通過 zmalloc_size() 函數(shù)來獲取分配內(nèi)存的大小, 也就是底層的分配器已經(jīng)維護(hù)好指針已分配內(nèi)存的大小, 那么PREFIX_SIZE就會設(shè)置成0, 如果HAVE_MALLOC_SIZE不存在, 則分配內(nèi)存的時(shí)候需要加上一個(gè) PREFIX_SIZE 的大小, 并且將申請的size寫到內(nèi)存首部的位置, 最后返回內(nèi)存首部的偏移地址作用指針給上層使用.

6.1 維護(hù) PREFIX_SIZE 變量的代碼

//如果有定義 HAVE_MALLOC_SIZE 變量
#ifdef HAVE_MALLOC_SIZE
//PREFIX_SIZE 用于保存指針對象的內(nèi)存長度, 第三方內(nèi)存分配器已經(jīng)存了內(nèi)存長度, 所以為 0
#define PREFIX_SIZE (0)
//定義 ASSERT_NO_SIZE_OVERFLOW 為空方法
#define ASSERT_NO_SIZE_OVERFLOW(sz)
#else
//沒有 HAVE_MALLOC_SIZE, 則定義保存內(nèi)存大小的字節(jié)
#if defined(__sun) || defined(__sparc) || defined(__sparc__)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
//定義ASSERT_NO_SIZE_OVERFLOW方法實(shí), sz表示申請內(nèi)存的大小, 斷言 申請內(nèi)存量+內(nèi)存大小PREFIX_SIZE 不會溢出
#define ASSERT_NO_SIZE_OVERFLOW(sz) assert((sz) + PREFIX_SIZE > (sz))
#endif

6.2 內(nèi)存分配malloc方法

zmalloc 和 zcalloc 的代碼流程基本一樣, 只是他們的底層調(diào)用不一樣, zmalloc 調(diào)用的是 malloc 方法, zcalloc 底層調(diào)用的是 calloc 方法.
tips : 理論上, 如果看懂了內(nèi)存分配方法的流程, 其他內(nèi)存分配的方法都很容易看懂的

//分配指定大小的內(nèi)存, 沒有分配成功, 則調(diào)用oom處理器
/* Allocate memory or panic */
void *zmalloc(size_t size) {
    void *ptr = ztrymalloc_usable(size, NULL);
    if (!ptr) zmalloc_oom_handler(size);
    return ptr;
}

//內(nèi)存溢出的函數(shù)指針
static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;

//內(nèi)存分配默認(rèn)的OOM錯(cuò)誤處理, 打印錯(cuò)誤日志, 并且退出程序
static void zmalloc_default_oom(size_t size) {
    //打印內(nèi)存異常日志
    fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
        size);
    fflush(stderr);
    //退出程序
    abort();
}

//嘗試分配內(nèi)存, 分配不了則返回 NULL
/* Try allocating memory, and return NULL if failed.
 * '*usable' is set to the usable size if non NULL. */
void *ztrymalloc_usable(size_t size, size_t *usable) {
    //判斷size是否溢出
    ASSERT_NO_SIZE_OVERFLOW(size);
    //分配內(nèi)存
    void *ptr = malloc(MALLOC_MIN_SIZE(size)+PREFIX_SIZE);

    //沒分配到, 則返回 NULL
    if (!ptr) return NULL;
// 有獲取分配內(nèi)存大小的方法
#ifdef HAVE_MALLOC_SIZE
    //獲取指針分配內(nèi)存的大小
    size = zmalloc_size(ptr);
    //更新內(nèi)存統(tǒng)計(jì)
    update_zmalloc_stat_alloc(size);
    //如果有指定 usable 指針, 則設(shè)置
    if (usable) *usable = size;
    //返回分配的指針
    return ptr;
#else
    //保存數(shù)據(jù)所需分配內(nèi)存的實(shí)際大小, 這里有點(diǎn)秀, int a = 1; *(&a)=2; 相當(dāng)于給a賦值為2
    //這里相當(dāng)于設(shè)置 PREFIX_SIZE 這段位置為 size
    *((size_t*)ptr) = size;
    //更新統(tǒng)計(jì)數(shù)據(jù)
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    //設(shè)置 usable
    if (usable) *usable = size;
    //計(jì)算出真正的指針, 也就是跳過PREFIX_SIZE大小后的內(nèi)存首地址
    return (char*)ptr+PREFIX_SIZE;
#endif
}

//增加內(nèi)存統(tǒng)計(jì), 原子增加
#define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n))
//減少內(nèi)存統(tǒng)計(jì), 原子減少
#define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n))

//用于統(tǒng)計(jì)已使用的內(nèi)存, 原子性變量
static redisAtomic size_t used_memory = 0;


  • 內(nèi)存分配zmalloc()方法首先調(diào)用ztrymalloc_usable()方法進(jìn)行內(nèi)存分配, 如果分配成功則返回指針, 否則返回NULL. zmalloc()函數(shù)默認(rèn)調(diào)用ztrymalloc_usable()進(jìn)行內(nèi)存分配, 如果分配成功則返回內(nèi)存指針, 否則返回NULL.
  • 如果分配失敗, 返回的指針為NULL, 則表示內(nèi)存溢出, 默認(rèn)的內(nèi)存溢出處理函數(shù)是先打印錯(cuò)誤日志, 再中斷程序.
  • ztrymalloc_usable() 方法主要作用是計(jì)算實(shí)際要分配的內(nèi)存(PREFIX_SIZE + SIZE)進(jìn)行分配, 然后增加內(nèi)存統(tǒng)計(jì). 這里會根據(jù)是否聲明 HAVE_MALLOC_SIZE 變量來決定是否調(diào)用 zmalloc_size() 方法來獲取內(nèi)存大小, 如果有聲明 HAVE_MALLOC_SIZE, 則直接將更新內(nèi)存統(tǒng)計(jì)并且將可用內(nèi)存大小返回, 如果沒聲明 HAVE_MALLOC_SIZE , 則要手工設(shè)置 PREFIX_SIZE 的值, 再更新內(nèi)存統(tǒng)計(jì)數(shù)據(jù), 最后返回真正的對象指針.

6.3 內(nèi)存重分配 zrealloc 方法

//內(nèi)存重分配方法, 分配不成功則報(bào)內(nèi)存溢出
/* Reallocate memory and zero it or panic */
void *zrealloc(void *ptr, size_t size) {
    //調(diào)用 ztryrealloc_usable() 方法進(jìn)行重分配
    ptr = ztryrealloc_usable(ptr, size, NULL);
    //如果指針不存在且要分配的內(nèi)存大于0, 則報(bào)內(nèi)存溢出
    if (!ptr && size != 0) zmalloc_oom_handler(size);
    //返回重分配后的指針
    return ptr;
}

//嘗試重分配內(nèi)存
/* Try reallocating memory, and return NULL if failed.
 * '*usable' is set to the usable size if non NULL. */
void *ztryrealloc_usable(void *ptr, size_t size, size_t *usable) {
    //斷言size + prefix_size 不溢出
    ASSERT_NO_SIZE_OVERFLOW(size);
//如果存在獲取內(nèi)存大小的方法
#ifndef HAVE_MALLOC_SIZE
    //舊的指針的原始指針(包括PREFIX_SIZE)
    void *realptr;
#endif
    //舊指針的內(nèi)存大小
    size_t oldsize;
    //新的指針
    void *newptr;

    //如果指針不為空且要分配的內(nèi)存長度為0, 則相當(dāng)于釋放內(nèi)存
    /* not allocating anything, just redirect to free. */
    if (size == 0 && ptr != NULL) {
        //釋放內(nèi)存
        zfree(ptr);
        //設(shè)置可使用內(nèi)存為0, 并且返回 NULL
        if (usable) *usable = 0;
        return NULL;
    }
    //如果指針為空, 則直接嘗試分配內(nèi)存
    /* Not freeing anything, just redirect to malloc. */
    if (ptr == NULL)
        return ztrymalloc_usable(size, usable);

//如果存在獲取內(nèi)存大小的方法
#ifdef HAVE_MALLOC_SIZE
    //獲取指針原來的內(nèi)存大小
    oldsize = zmalloc_size(ptr);
    //重分配給定大小內(nèi)存
    newptr = realloc(ptr,size);
    //沒有分配到, 直接返回 NULL
    if (newptr == NULL) {
        if (usable) *usable = 0;
        return NULL;
    }

    //減少舊的內(nèi)存統(tǒng)計(jì)
    update_zmalloc_stat_free(oldsize);
    //獲取新分配的內(nèi)存大小
    size = zmalloc_size(newptr);
    //添加內(nèi)存統(tǒng)計(jì)
    update_zmalloc_stat_alloc(size);
    //設(shè)置可用內(nèi)存大小
    if (usable) *usable = size;
    //返回新的指針
    return newptr;
#else
    //獲取指向的頭部的原始指針
    realptr = (char*)ptr-PREFIX_SIZE;
    //獲取 PREFIX_SIZE 的值, 也就是內(nèi)存的大小
    oldsize = *((size_t*)realptr);
    //重新分配
    newptr = realloc(realptr,size+PREFIX_SIZE);
    //沒有分配成功, 則返回 NULL
    if (newptr == NULL) {
        if (usable) *usable = 0;
        return NULL;
    }

    //設(shè)置 PREFIX_SIZE 的值
    *((size_t*)newptr) = size;
    //注意: 重分配內(nèi)存時(shí), 更新內(nèi)存統(tǒng)計(jì)是沒有操作PREFIX_SIZE的, 因?yàn)镻REFIX_SIZE是沒有變化的, 第一次內(nèi)存分配時(shí)已經(jīng)將PREFIX_SIZE納入了
    //所以, 這里只要重新申請的size就行了
    //減少舊內(nèi)存的統(tǒng)計(jì)
    update_zmalloc_stat_free(oldsize);
    //更新新的內(nèi)存
    update_zmalloc_stat_alloc(size);
    //設(shè)置可用內(nèi)存大小
    if (usable) *usable = size;
    //計(jì)算出可用的指針
    return (char*)newptr+PREFIX_SIZE;
#endif
}
  • zrealloc()方法通過調(diào)用ztryrealloc_usable()方法進(jìn)行重分配內(nèi)存, 如果返回的指針為空且要分配的內(nèi)存不為0, 則報(bào)內(nèi)存溢出
  • ztryrealloc_usable()方法的作用是嘗試重分配內(nèi)存并且返回分配后的可用內(nèi)存. 這方法首先處理兩種極端情況, 一是當(dāng)要分配的內(nèi)存size為0且原來內(nèi)存指針不為空, 則相當(dāng)于釋放內(nèi)存, 二是當(dāng)原指針為空, 則直接根據(jù)size分配內(nèi)存. 也就是根據(jù)參數(shù)不同, zrealloc()方法可以當(dāng)作zmalloc()和zfree()使用.
  • 極端情況處理完后, 就開始處理正常的內(nèi)存重分配.
  • 正常的內(nèi)存重分配也分兩種情況處理, 存在獲取已分配內(nèi)存大小的方法時(shí), 則獲取原來的已分配的內(nèi)存大小用于減少內(nèi)存統(tǒng)計(jì), 然后重新分配內(nèi)存, 重新統(tǒng)計(jì)已分配的內(nèi)存. 如果不存在獲取已分配內(nèi)存大小的方法, 則需要手工計(jì)算出原已分配內(nèi)存的大小, 然后重新分配內(nèi)存并且設(shè)置新的內(nèi)存大小到PREFIX_SIZE中, 最后先減少舊內(nèi)存統(tǒng)計(jì)再添加新的內(nèi)存統(tǒng)計(jì), 返回對象可用的指針.

6.4 內(nèi)存釋放

內(nèi)存釋放有兩個(gè)方法, 分別是zfree()和zfree_usable().


//釋放指針內(nèi)存
void zfree(void *ptr) {
//存在獲取內(nèi)存大小的方法
#ifndef HAVE_MALLOC_SIZE
    void *realptr;
    size_t oldsize;
#endif

    //如果指針為空, 直接返回
    if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
    //減少內(nèi)存統(tǒng)計(jì)
    update_zmalloc_stat_free(zmalloc_size(ptr));
    //釋放指針
    free(ptr);
#else
    //計(jì)算出原始指針
    realptr = (char*)ptr-PREFIX_SIZE;
    //獲取內(nèi)存大小
    oldsize = *((size_t*)realptr);
    //減少內(nèi)存統(tǒng)計(jì), 包括 PREFIX_SIZE
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    //釋放指針
    free(realptr);
#endif
}

//跟zfree相同, *usable表示釋放內(nèi)存的大小
/* Similar to zfree, '*usable' is set to the usable size being freed. */
void zfree_usable(void *ptr, size_t *usable) {
#ifndef HAVE_MALLOC_SIZE
    //原始指針
    void *realptr;
    //舊內(nèi)存
    size_t oldsize;
#endif

    //如果指針為空, 則直接返回
    if (ptr == NULL) return;
//存在獲取內(nèi)存大小的方法
#ifdef HAVE_MALLOC_SIZE
//獲取內(nèi)存大小, 并且減小內(nèi)存統(tǒng)計(jì)
    update_zmalloc_stat_free(*usable = zmalloc_size(ptr));
    //釋放指針
    free(ptr);
#else
    //計(jì)算出原始指針
    realptr = (char*)ptr-PREFIX_SIZE;
    //獲取內(nèi)存大小
    *usable = oldsize = *((size_t*)realptr);
    //減少內(nèi)存統(tǒng)計(jì)
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    //釋放內(nèi)存
    free(realptr);
#endif
}
  • zfree() 方法也是根據(jù)是否存在獲取已分配內(nèi)存大小方法來做不同的處理, 存在的話, 則直接獲取舊內(nèi)存大小用于減少內(nèi)存統(tǒng)計(jì), 然后直接釋放指針, 不存在的話, 需要手動(dòng)解析PREFIX_SIZE的值來減少內(nèi)存統(tǒng)計(jì), 然后根據(jù)對象指針和PREFIX_SIZE來還原原始指針, 再進(jìn)行釋放.
  • zfree_usable()方法和zfree()差不多, 只是會將可用的內(nèi)存大小返回.

6.5 手動(dòng)實(shí)現(xiàn)獲取內(nèi)存大小的方法

這兩個(gè)方法, 估計(jì)是給上層使用的

//如果沒有獲取內(nèi)存分配大小的方法
#ifndef HAVE_MALLOC_SIZE
//獲取指針真實(shí)分配內(nèi)存
size_t zmalloc_size(void *ptr) {
    void *realptr = (char*)ptr-PREFIX_SIZE;
    size_t size = *((size_t*)realptr);
    return size+PREFIX_SIZE;
}
//獲取可用內(nèi)存
size_t zmalloc_usable_size(void *ptr) {
    return zmalloc_size(ptr)-PREFIX_SIZE;
}
#endif

6.6 其他方法

//復(fù)制字符串
char *zstrdup(const char *s) {
    //獲取字符串的長度, 字符串長度 + 字符串結(jié)束符(1)
    size_t l = strlen(s)+1;
    //分配內(nèi)存
    char *p = zmalloc(l);

    //將字符串s的內(nèi)容拷貝到指針p中
    memcpy(p,s,l);
    return p;
}

//獲取已分配的內(nèi)存
size_t zmalloc_used_memory(void) {
    size_t um;
    //將 used_memory 原子獲取并寫入到 um 中
    atomicGet(used_memory,um);
    return um;
}

//設(shè)置內(nèi)存溢出處理器
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
    zmalloc_oom_handler = oom_handler;
}

微信文章鏈接: https://mp.weixin.qq.com/s?__biz=MzI3NDAxNTAzMw==&mid=2247483712&idx=1&sn=fd0e43107dc86e946a969f420ec472fc&chksm=eb1b3716dc6cbe00d9f85830458bb7f2725ecb631f502f2fc676c0d763b65f1b58016f7dc74b&token=122962709&lang=zh_CN#rd

個(gè)人微信: wolfleong

微信公眾號: wolfleong

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容