FFmpeg 源码之内存管理函数族

这些函数负责分配、释放和复制内存。所有内存分配函数都有一个内置的 INT_MAX 字节上限。这可以通过 av_max_alloc() 进行更改,但要非常小心。

一、av_malloc

分配一个适合所有内存访问的对齐内存块(包括 CPU 上可用的向量)。

  1. 当存在 HAVE_POSIX_MEMALIGN 条件,实际分配工作是 posix_memalign(…) 完成的,此函数是 POSIX 1003.1d 提出的;
  2. 如果条件 HAVE_ALIGNED_MALLOC 为真,则使用 _aligned_malloc(…) 做真正的分配,查阅资料得知此函数是 C 标准新增的 windows 下动态申请对齐内存函数;
  3. 如果条件 HAVE_MEMALIGN 为真,则实际是由 memalign(…) 进行分配的;
  4. 只有以上的编译条件都不满足才会使用我们常见的 malloc(…) 做分配。

函数 memalign(…) 并非在所有 UNIX 实现上都存在。大多数提供 memalign(…) 的其他 UNIX 实现都要求引用 <stdlib.h> 而非 <malloc.h> 以获得函数声明。

SUSv3 并未纳入 memalign(…),而是规范了一个类似函数,也就是 posix_memalign(…)。

libavutil/mem.c

#define ALIGN (HAVE_AVX512 ? 64 : (HAVE_AVX ? 32 : 16))
...
static size_t max_alloc_size= INT_MAX;
...
void *av_malloc(size_t size)
{
    void *ptr = NULL;

    if (size > max_alloc_size)
        return NULL;

#if HAVE_POSIX_MEMALIGN
    if (size) //OS X on SDK 10.6 has a broken posix_memalign implementation
    if (posix_memalign(&ptr, ALIGN, size))
        ptr = NULL;
#elif HAVE_ALIGNED_MALLOC
    ptr = _aligned_malloc(size, ALIGN);
#elif HAVE_MEMALIGN
#ifndef __DJGPP__
    ptr = memalign(ALIGN, size);
#else
    ptr = memalign(size, ALIGN);
#endif
    /* Why 64?
     * Indeed, we should align it:
     *   on  4 for 386
     *   on 16 for 486
     *   on 32 for 586, PPro - K6-III
     *   on 64 for K7 (maybe for P3 too).
     * Because L1 and L2 caches are aligned on those values.
     * But I don't want to code such logic here!
     */
    /* Why 32?
     * For AVX ASM. SSE / NEON needs only 16.
     * Why not larger? Because I did not see a difference in benchmarks ...
     */
    /* benchmarks with P3
     * memalign(64) + 1          3071, 3051, 3032
     * memalign(64) + 2          3051, 3032, 3041
     * memalign(64) + 4          2911, 2896, 2915
     * memalign(64) + 8          2545, 2554, 2550
     * memalign(64) + 16         2543, 2572, 2563
     * memalign(64) + 32         2546, 2545, 2571
     * memalign(64) + 64         2570, 2533, 2558
     *
     * BTW, malloc seems to do 8-byte alignment by default here.
     */
#else
    ptr = malloc(size);
#endif
    if(!ptr && !size) {
        size = 1;
        ptr= av_malloc(1);
    }
#if CONFIG_MEMORY_POISONING
    if (ptr)
        memset(ptr, FF_MEMORY_POISON, size);
#endif
    return ptr;
}

二、av_mallocz

此函数比 av_malloc 多一个动作,会将分配块的所有字节置零。这也是命名多加一个“z”的含义,zero 的意思。

看源码确实也是在调用 av_malloc 后,又接了 memset 置 0 操作。

libavutil/mem.c

void *av_mallocz(size_t size)
{
    void *ptr = av_malloc(size);
    if (ptr)
        memset(ptr, 0, size);
    return ptr;
}

三、av_free

此函数释放一个用 av_malloc() 或 av_realloc() 族函数分配的内存块。比如 av_mallocz 分配的内存也可以用它释放。建议使用 av_freep() 代替,以防止留下悬空指针。

_aligned_free 是搭配释放 _aligned_malloc 申请的内存,其他的内存释放由 free 执行。

libavutil/mem.c

void av_free(void *ptr)
{
#if HAVE_ALIGNED_MALLOC
    _aligned_free(ptr);
#else
    free(ptr);
#endif
}

四、av_freep

比 av_freep 多了一步,释放内存后将指向它的指针设置为 NULL。val 指针是个局部变量,在栈帧中分配,函数出栈后会自动清理。

libavutil/mem.c

void av_freep(void *arg)
{
    void *val;

    memcpy(&val, arg, sizeof(val));
    memcpy(arg, &(void *){ NULL }, sizeof(val));
    av_free(val);
}

五、av_size_mult

乘以两个 size_t 值检查是否溢出。返回 0 代表没有溢出,否则返回 AVERROR(EINVAL)。

a,b —— 乘法操作数

r —— 指向操作结果的指针,即 a * b 的值

libavutil/mem.h

static inline int av_size_mult(size_t a, size_t b, size_t *r)
{
    size_t t = a * b;
    /* Hack inspired from glibc: don't try the division if nelem and elsize
     * are both less than sqrt(SIZE_MAX). */
    if ((a | b) >= ((size_t)1 << (sizeof(size_t) * 4)) && a && t / a != b)
        return AVERROR(EINVAL);
    *r = t;
    return 0;
}

六、av_malloc_array

nmemb —— 猜测应该是数组中单个元素占用的字节数;

size —— 数组的 size。

首先调用 av_size_mult 检测 nmemb * size 是否存在溢出,如果不存在就直接调用 av_malloc 做实际的分配动作。否则返回 NULL。

libavutil/mem.c

void *av_malloc_array(size_t nmemb, size_t size)
{
    size_t result;
    if (av_size_mult(nmemb, size, &result) < 0)
        return NULL;
    return av_malloc(result);
}

七、av_mallocz_array

和 av_malloc_array 唯一区别在于内部分配内存函数调用 av_mallocz 实现,也就是分配的内存块会清零。

libavutil/mem.c

void *av_mallocz_array(size_t nmemb, size_t size)
{
    size_t result;
    if (av_size_mult(nmemb, size, &result) < 0)
        return NULL;
    return av_mallocz(result);
}

八、av_calloc

等效于 av_mallocz_array() 的非内联函数。为与 calloc() C 函数对称而创建。从源码看和 av_mallocz_array 源码一样。

libavutil/mem.c

void *av_calloc(size_t nmemb, size_t size)
{
    size_t result;
    if (av_size_mult(nmemb, size, &result) < 0)
        return NULL;
    return av_mallocz(result);
}

九、av_realloc

分配、重新分配或释放一块内存。如果 ptr 为 NULL 并且 size > 0,分配一个新的块。如果 size 为 0,释放 ptr 所指向的内存块。否则,根据大小扩展或缩小该内存块。返回指针指向一个重新分配的块,如果该块不能重新分配,或者该函数被用来释放内存块,则为 NULL。与 av_malloc() 不同,返回的指针不能保证正确对齐。

re 前缀就是重复的意思。根据条件编译选项,要么选择 _aligned_realloc 做重新分配内存的动作,要么使用 realloc 实现。

libavutil/mem.c

void *av_realloc(void *ptr, size_t size)
{
    if (size > max_alloc_size)
        return NULL;

#if HAVE_ALIGNED_MALLOC
    return _aligned_realloc(ptr, size + !size, ALIGN);
#else
    return realloc(ptr, size + !size);
#endif
}

十、av_reallocp

通过指向指针的指针分配、重新分配或释放一块内存。

如果 *ptr 是 NULL 并且 size > 0,分配一个新的块。如果 size 为 0,释放 *ptr 所指向的内存块。否则,根据大小扩展或缩小该内存块。ptr 指针,指向已经由 av_realloc() 分配的内存块的指针,或者指向 NULL 的指针。指针成功时更新,失败时释放。

和 av_realloc 类似,只不过返回值变成了 int 型,成功返回 0 。失败返回 AVERROR(ENOMEM)。

libavutil/mem.c

int av_reallocp(void *ptr, size_t size)
{
    void *val;

    if (!size) {
        av_freep(ptr);
        return 0;
    }

    memcpy(&val, ptr, sizeof(val));
    val = av_realloc(val, size);

    if (!val) {
        av_freep(ptr);
        return AVERROR(ENOMEM);
    }

    memcpy(ptr, &val, sizeof(val));
    return 0;
}

十一、av_realloc_f

分配、重新分配或释放一块内存。这个函数与 av_realloc() 做同样的事情,除了:

  1. 它接受两个 size 参数,并在检查整数溢出的乘法结果后分配 nelem * elsize 字节;
  2. 它在失败的情况下释放输入块,从而避免了传统的内存泄漏。

libavutil/mem.c

void *av_realloc_f(void *ptr, size_t nelem, size_t elsize)
{
    size_t size;
    void *r;

    if (av_size_mult(elsize, nelem, &size)) {
        av_free(ptr);
        return NULL;
    }
    r = av_realloc(ptr, size);
    if (!r)
        av_free(ptr);
    return r;
}

十二、av_realloc_array

首先调用 av_size_mult 检测 nmemb * size 是否存在溢出,如果不存在就直接调用 av_realloc 做实际的重新分配动作。否则返回 NULL。

libavutil/mem.c

void *av_realloc_array(void *ptr, size_t nmemb, size_t size)
{
    size_t result;
    if (av_size_mult(nmemb, size, &result) < 0)
        return NULL;
    return av_realloc(ptr, result);
}

十三、av_reallocp_array

指针版本的 av_realloc_array,内部调用了 av_realloc_f 函数做实际再分配工作。

libavutil/mem.c

int av_reallocp_array(void *ptr, size_t nmemb, size_t size)
{
    void *val;

    memcpy(&val, ptr, sizeof(val));
    val = av_realloc_f(val, nmemb, size);
    memcpy(ptr, &val, sizeof(val));
    if (!val && nmemb && size)
        return AVERROR(ENOMEM);

    return 0;
}

十四、av_fast_realloc

如果给定的缓冲区不够大,则重新分配它,否则什么也不做。如果给定的缓冲区为 NULL,则分配一个新的未初始化的缓冲区。

如果给定的缓冲区不够大,并且重新分配失败,则返回 NULL 并将 *size 设置为 0,但原始缓冲区不会被更改或释放。

典型的使用模式如下:

uint8_t *buf = ...;
uint8_t *new_buf = av_fast_realloc(buf, &current_size, size_needed);
if (!new_buf) {
    // Allocation failed; clean up original buffer
    av_freep(&buf);
    return AVERROR(ENOMEM);
}

size —— 指向缓冲区 ptr 当前大小的指针。*size 在成功时改为 min_size,失败时改为 0。

min_size 会重新在内部计算,一般情况下等于 min_size + min_size / 16 + 32。

libavutil/mem.c

void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size)
{
    if (min_size <= *size)
        return ptr;

    if (min_size > max_alloc_size) {
        *size = 0;
        return NULL;
    }

    min_size = FFMIN(max_alloc_size, FFMAX(min_size + min_size / 16 + 32, min_size));

    ptr = av_realloc(ptr, min_size);
    /* we could set this to the unmodified min_size but this is safer
     * if the user lost the ptr and uses NULL now
     */
    if (!ptr)
        min_size = 0;

    *size = min_size;

    return ptr;
}

十五、av_fast_malloc

分配一个缓冲区,如果足够大,则重用给定的缓冲区。

与 av_fast_realloc() 相反,当前缓冲区内容可能不会被保留,并且在错误时释放旧缓冲区,因此不需要进行特殊处理来避免内存泄漏。

*ptr 允许为 NULL,在这种情况下,如果 size_needed 大于 0,分配总是会发生。

uint8_t *buf = ...;
av_fast_malloc(&buf, &current_size, size_needed);
if (!buf) {
    // 分配失败, buf 已经释放
    return AVERROR(ENOMEM);
}

av_fast_malloc 内部实际调用了 ff_fast_malloc 内联函数,编译的时候会展开。

ff_fast_malloc 函数流程:

  1. min_size 小于传入的 *size 直接返回 0;
  2. 重新计算 min_size,释放传入的 ptr 指针指向的旧内存块;
  3. 根据传入的 zero_realloc 去调用 av_mallocz 或 av_malloc 做实际分配工作;
  4. 给 ptr 指针赋值刚刚分配的内存块地址,*size 赋为重新计算的 min_size;

成功时返回 1,否则返回 0。

libavutil/mem_internal.h

static inline int ff_fast_malloc(void *ptr, unsigned int *size, size_t min_size, int zero_realloc)
{
    void *val;

    memcpy(&val, ptr, sizeof(val));
    if (min_size <= *size) {
        av_assert0(val || !min_size);
        return 0;
    }
    min_size = FFMAX(min_size + min_size / 16 + 32, min_size);
    av_freep(ptr);
    val = zero_realloc ? av_mallocz(min_size) : av_malloc(min_size);
    memcpy(ptr, &val, sizeof(val));
    if (!val)
        min_size = 0;
    *size = min_size;
    return 1;
}

libavutil/mem.c

void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size)
{
    ff_fast_malloc(ptr, size, min_size, 0);
}

十六、av_fast_mallocz

av_fast_mallocz 和 av_fast_malloc 唯一的不同在调用 ff_fast_malloc 时传入的最后一个参数,意味着内部实际会由 av_mallocz 做分配工作。

libavutil/mem.c

void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size)
{
    ff_fast_malloc(ptr, size, min_size, 1);
}

十七、av_strdup

复制一个字符串。返回指向新分配的字符串的指针,该字符串包含 s 的副本,如果不能分配该字符串,则为 NULL。

  1. 先调用 strlen 计算字符串 s 的长度加 1(容纳结尾);
  2. 调用 av_realloc 分配内存;
  3. 调用 memcpy 将 s 指向的字符串复制到新分配的内存块;
  4. 返回新的字符串指针。

libavutil/mem.c

char *av_strdup(const char *s)
{
    char *ptr = NULL;
    if (s) {
        size_t len = strlen(s) + 1;
        ptr = av_realloc(NULL, len);
        if (ptr)
            memcpy(ptr, s, len);
    }
    return ptr;
}

十八、av_strndup

复制一个字符串的子字符串。返回指向新分配的字符串的指针,该字符串包含 s 的子字符串,如果不能分配该字符串,则为 NULL。

memchr 函数原型 extern void *memchr(const void *buf, int ch, size_t count)
功能:从 buf 所指内存区域的前 count 个字节查找字符 ch。

  1. 调用 memchr 查找 s 中 len 长度存在的 0 字符;
  2. 计算出子串的长度 len;
  3. 调用 av_realloc 分配子串占用的空间;
  4. 调用 memcpy 将子串复制到刚刚分配的空间;
  5. 返回指向子串的指针。

libavutil/mem.c

char *av_strndup(const char *s, size_t len)
{
    char *ret = NULL, *end;

    if (!s)
        return NULL;

    end = memchr(s, 0, len);
    if (end)
        len = end - s;

    ret = av_realloc(NULL, len + 1);
    if (!ret)
        return NULL;

    memcpy(ret, s, len);
    ret[len] = 0;
    return ret;
}

十九、av_memdup

使用 av_malloc() 复制缓冲区。返回指向一个新分配的包含 p 副本的缓冲区的指针,如果该缓冲区不能被分配,则为 NULL。

调用 av_malloc 分配内存,然后调用 memcpy 复制内存块。

libavutil/mem.c

void *av_memdup(const void *p, size_t size)
{
    void *ptr = NULL;
    if (p) {
        ptr = av_malloc(size);
        if (ptr)
            memcpy(ptr, p, size);
    }
    return ptr;
}

二十、av_memcpy_backptr

重叠 memcpy() 实现。

dst —— 目的地 buffer;

back —— 返回开始复制的字节数 (即重叠窗口的初始大小);必须大于 0;

cnt —— 要复制的字节数;必须 >= 0。

libavutil/mem.c

void av_memcpy_backptr(uint8_t *dst, int back, int cnt)
{
    const uint8_t *src = &dst[-back];
    if (!back)
        return;

    if (back == 1) {
        memset(dst, *src, cnt);
    } else if (back == 2) {
        fill16(dst, cnt);
    } else if (back == 3) {
        fill24(dst, cnt);
    } else if (back == 4) {
        fill32(dst, cnt);
    } else {
        if (cnt >= 16) {
            int blocklen = back;
            while (cnt > blocklen) {
                memcpy(dst, src, blocklen);
                dst       += blocklen;
                cnt       -= blocklen;
                blocklen <<= 1;
            }
            memcpy(dst, src, cnt);
            return;
        }
        if (cnt >= 8) {
            AV_COPY32U(dst,     src);
            AV_COPY32U(dst + 4, src + 4);
            src += 8;
            dst += 8;
            cnt -= 8;
        }
        if (cnt >= 4) {
            AV_COPY32U(dst, src);
            src += 4;
            dst += 4;
            cnt -= 4;
        }
        if (cnt >= 2) {
            AV_COPY16U(dst, src);
            src += 2;
            dst += 2;
            cnt -= 2;
        }
        if (cnt)
            *dst = *src;
    }
}

参考资料:

1.http://www.ffmpeg.org/doxygen/4.1/group__lavu__mem__funcs.html

2.《Linux/UNIX 系统编程手册(上册)》

tyyj90 CSDN认证博客专家 安卓全栈开发
Android高级开发工程师,熟悉App开发,熟悉Framework定制开发,略了解Linux内核。开发使用的语言涉及Java、Kotlin、C/C++、Python和汇编NEON指令集。
相关推荐
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值