zmalloc

zmalloc 模块是 Redis 对 stdlib 中内存管理函数 mallocreallocfree 等的再封装,Redis 内部的动态内存分配均通过此模块实现。

zmalloc 函数实现解析

static size_t used_memory = 0;

void *zmalloc(size_t size) {
    void *ptr = malloc(size+sizeof(size_t));

    if (!ptr) return NULL;
#ifdef HAVE_MALLOC_SIZE
    used_memory += redis_malloc_size(ptr);
    return ptr;
#else
    *((size_t*)ptr) = size;
    used_memory += size+sizeof(size_t);
    return (char*)ptr+sizeof(size_t);
#endif
}

这里存在一些由于操作系统差异的额外代码,我们暂且忽略

static size_t used_memory = 0;

void *zmalloc(size_t size) {
    void *ptr = malloc(size+sizeof(size_t));
    if (!ptr) return NULL;
    *((size_t*)ptr) = size;
    used_memory += size+sizeof(size_t);
    return (char*)ptr+sizeof(size_t);
}

这里有一个比较迷惑的点:函数自身接受一个参数 size,即需要动态内存分配的大小(字节数),函数随后便将该值传递给 malloc 函数进行内存管理,但在传递的时候却又给 size 加上了一个 size_t 占用的空间。

这其实不难理解,让我们先往下看

if (!ptr) return NULL; // 如果分配失败(内存已满)返回 NULL
*((size_t*)ptr) = size; // 给已分配内存的前若干位(取决于 size_t 的大小)赋值为 size
used_memory += size+sizeof(size_t); // 记录已使用内存

我们再看看 zmalloc 中 zfree 函数的实现:

void zfree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE
    void *realptr;
    size_t oldsize;
#endif

    if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
    used_memory -= redis_malloc_size(ptr);
    free(ptr);
#else
    realptr = (char*)ptr-sizeof(size_t);
    oldsize = *((size_t*)realptr);
    used_memory -= oldsize+sizeof(size_t);
    free(realptr);
#endif
}

我们同样省略掉处理跨平台的额外代码,

void zfree(void *ptr) {
    void *realptr;
    size_t oldsize;
    realptr = (char*)ptr-sizeof(size_t);
    oldsize = *((size_t*)realptr);
    used_memory -= oldsize+sizeof(size_t);
    free(realptr);
}

通过综合这两段代码我们便不难读懂,上文 zmalloc 在分配内存时额外请求了一个 size_t 的空间,并在这个空间中存储调用方实际需要的空间大小。

再看 zmalloc 函数的 return 语句,

return (char*)ptr+sizeof(size_t);

该语句将 ptr 转化为 char 指针类型,再直接使用指针运算,加上一个 size_t 所需的空间。

假设这是调用方实际需要的内存大小:
---------
| | | | |
---------

申请内存时加上了 sizeof(size_t),实际申请到的内存:
-----------
| | | | | |
-----------

在第一个格子填上调用方需要的内存大小:
-----------
|4| | | | |
-----------

最后在返回指向这块内存时进行指针运算,防止调用方写入到存储内存大小的第一块内存:
-----------       -----------
|4| | | | |       |4| | | | |
-----------  -->  -----------
^                   ^
|                   |

为什么要转化为 char 指针类型?因为 char 占用内存的大小正好为一个字节,转化为 char 指针类型再进行运算更方便。当然,你也可以是使用

(size_t*)ptr + 1

效果相同。

zfree 函数实现解析

void zfree(void *ptr) {
    void *realptr;
    size_t oldsize;
    realptr = (char*)ptr-sizeof(size_t); // 此处即为 ``(char*)ptr+sizeof(size_t)`` 的逆运算,获取 malloc 时实际返回的指针
    oldsize = *((size_t*)realptr); // 获取 zmalloc 时,调用方实际需要的内存大小
    used_memory -= oldsize+sizeof(size_t);
    free(realptr);
}

点此查看原文