参考n4.4.2版本中ibavutil/mem.c,主要代码早期放在libavcodec/utils.c,后来分拆过来,文件顶部注释中有FFMPEG初版作者大神Fabrice Bellard的留名。

1. 基本API

1.1. av_max_alloc

设置一系列内存申请API的最大长度:

  • av_malloc
  • av_realloc
  • av_calloc
  • av_fast_realloc
1
2
3
4
5
static size_t max_alloc_size= INT_MAX;

void av_max_alloc(size_t max){
    max_alloc_size = max;
}

默认INT_MAX是2147483647,即0x7FFFFFFF,约2GB字节。

1.2. av_malloc

相关API:

  • av_mallocz
  • av_realloc
  • av_calloc

首先通过条件编译跳到特定的字节对齐分配子函数,没有符合条件时调用通用的malloc()。注释中提到386、486、586、K7等CPU,对齐是因为和CPU的L1和L2 cache line长度保持一致,提高访问效率。

以前翻过一本Intel中国员工汇编的一本书,专门讲cache优化,还有浩哥的这篇文章:与程序员相关的CPU缓存知识

1.3. av_mallocz

调用av_malloc后初始化为0。

1.4. av_free

对应av_malloc,释放内存。

1.5. av_freep

1
2
3
4
5
6
7
8
void av_freep(void *arg)
{
    void *val;

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

函数参数类型是void *,是一个二级指针数值,对其取值的结果是av_malloc()或其他分配函数的返回值,一个合法的堆空间地址,比如如下使用:

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, char **argv)
{
    chhar *p = (char *) av_malloc(8192);

    if (p)  {
        *p = 'X';
        av_freep(&p);
    }

    return 0;
}

通过av_freep在释放内存之外可以将p修改为NULL,节省每个调用处都另加一行主动修改为NULL的代码,并且还使得一旦代码编写错误,非法访问释放过的p会立即崩溃的特点,让故障尽快出现,而不是破坏内存使得更难追查。

如果觉得有点绕,可以把void *想象成一个64位的整型。

换作我写的笨一些:

1
2
3
4
5
6
7
void av_freep(void *arg)
{
    void **val = (void **) arg;

    av_free(*val);
    *val = NULL;
}

查看git log发现这个函数的较早版本和我的写法一样,直到2015年这个Commit:mem: fix pointer pointer aliasing violations,改用memcpy替代直接变量复制。

提交注释如下:

mem: fix pointer pointer aliasing violations This uses explicit memory copying to read and write pointer to pointers of arbitrary object types. This works provided that the architecture uses the same representation for all pointer types (the previous code made that assumption already anyway).

3. 数组

3.1. 分配数组av_malloc_array

相关API:

  • av_mallocz_array
  • av_rloealc_array
  • av_reallocp_array
1
2
3
4
5
6
7
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);
}

分配连续的nmemb个长度为size的内存空间,内部av_size_mult()实现中有一行代码判断两个size_t类型变量相乘是否溢出:

1
2
3
4
5
6
7
8
9
10
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;
}

3.2. 动态数组的添加:av_dynarray_add

相关API:

  • av_dynarray_add_nofree
  • av_dynarray2_add
1
2
3
4
5
6
7
8
9
10
11
12
13
void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem)
{
    void **tab;
    memcpy(&tab, tab_ptr, sizeof(tab));

    FF_DYNARRAY_ADD(INT_MAX, sizeof(*tab), tab, *nb_ptr, {
        tab[*nb_ptr] = elem;
        memcpy(tab_ptr, &tab, sizeof(tab));
    }, {
        *nb_ptr = 0;
        av_freep(tab_ptr);
    });
}

关键实现FF_DYNARRAY_ADD是一个复杂的宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define FF_DYNARRAY_ADD(av_size_max, av_elt_size, av_array, av_size, \
                        av_success, av_failure) \
    do { \
        size_t av_size_new = (av_size); \
        if (!((av_size) & ((av_size) - 1))) { \
            av_size_new = (av_size) ? (av_size) << 1 : 1; \
            if (av_size_new > (av_size_max) / (av_elt_size)) { \
                av_size_new = 0; \
            } else { \
                void *av_array_new = \
                    av_realloc((av_array), av_size_new * (av_elt_size)); \
                if (!av_array_new) \
                    av_size_new = 0; \
                else \
                    (av_array) = av_array_new; \
            } \
        } \
        if (av_size_new) { \
            { av_success } \
            (av_size)++; \
        } else { \
            av_failure \
        } \
    } while (0)

利用av_realloc反复申请空间。

4. 快速分配

4.1. av_fast_malloc

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

ptr是二级指针,先用av_freep释放,再把申请到的新空间地址存储到她的内存中,避免调用者编写多行代码。

5. av_memcpy_backptr

void av_memcpy_backptr(uint8_t *dst, int back, int cnt)

代码较长不贴出来了,大概是实现从dst往前数若干个字节的位置开始,复制数据填写到其后,为提高效率使用了汇编指令。