这些函数负责分配、释放和复制内存。所有内存分配函数都有一个内置的 INT_MAX 字节上限。这可以通过 av_max_alloc() 进行更改,但要非常小心。
一、av_malloc
分配一个适合所有内存访问的对齐内存块(包括 CPU 上可用的向量)。
- 当存在 HAVE_POSIX_MEMALIGN 条件,实际分配工作是 posix_memalign(…) 完成的,此函数是 POSIX 1003.1d 提出的;
- 如果条件 HAVE_ALIGNED_MALLOC 为真,则使用 _aligned_malloc(…) 做真正的分配,查阅资料得知此函数是 C 标准新增的 windows 下动态申请对齐内存函数;
- 如果条件 HAVE_MEMALIGN 为真,则实际是由 memalign(…) 进行分配的;
- 只有以上的编译条件都不满足才会使用我们常见的 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() 做同样的事情,除了:
- 它接受两个 size 参数,并在检查整数溢出的乘法结果后分配 nelem * elsize 字节;
- 它在失败的情况下释放输入块,从而避免了传统的内存泄漏。
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, ¤t_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, ¤t_size, size_needed);
if (!buf) {
// 分配失败, buf 已经释放
return AVERROR(ENOMEM);
}
av_fast_malloc 内部实际调用了 ff_fast_malloc 内联函数,编译的时候会展开。
ff_fast_malloc 函数流程:
- min_size 小于传入的 *size 直接返回 0;
- 重新计算 min_size,释放传入的 ptr 指针指向的旧内存块;
- 根据传入的 zero_realloc 去调用 av_mallocz 或 av_malloc 做实际分配工作;
- 给 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。
- 先调用 strlen 计算字符串 s 的长度加 1(容纳结尾);
- 调用 av_realloc 分配内存;
- 调用 memcpy 将 s 指向的字符串复制到新分配的内存块;
- 返回新的字符串指针。
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。
- 调用 memchr 查找 s 中 len 长度存在的 0 字符;
- 计算出子串的长度 len;
- 调用 av_realloc 分配子串占用的空间;
- 调用 memcpy 将子串复制到刚刚分配的空间;
- 返回指向子串的指针。
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 系统编程手册(上册)》