版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
mem.c
mem.c中的函数负责FFmpeg中的内存操作,内存的分配、重新分配、释放等。最常使用的几个函数:av_malloc(),av_realloc(),av_mallocz(),av_calloc(),av_free(),av_freep()。
c语言基础
源码分析前,先看一下当中用到的几个c常用函数
1.C/C++中extern关键字详解
extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
此外extern也可用来进行链接指定,也就是说extern有两个作用,第一个,当它与"C"一起连用时,
如: extern "C" void fun(int a, int b);
则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,
可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的"脾气"了(不同的编译器采用的方法不一样)。
为什么这么做呢,因为C++支持函数的重载啊,在这里不去过多的论述这个问题,如果你有兴趣可以去网上搜索,相信你可以得到满意的解释!
第二,当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int;
它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块活其他模块中使用。
记住它是一个声明不是定义,也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可。
在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。
2.memset
memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。
memset()函数原型是extern void *memset(void *buffer, int c, int count) buffer:为指针或是数组,c:是赋给buffer的值,count:是buffer的长度.
3.memcpy
memcpy指的是C和C++使用的内存拷贝函数,函数原型为void *memcpy(void *destin, void *source, unsigned n);
函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中,即从源source中拷贝n个字节到目标destin中。
4.realloc()
realloc() 对 ptr 指向的内存重新分配 size 大小的空间,size 可比原来的大或者小,还可以不变。
当 malloc()、calloc() 分配的内存空间不够用时,就可以用 realloc() 来调整已分配的内存。
内存对齐(图文均转自雷神博客)
这里转述雷神博客——FFmpeg源代码简单分析:内存的分配和释放(av_malloc()、av_free()等)中的内容,因为特别通俗易懂,所以原封不动转了过来~
程序员通常认为内存就是一个字节数组,每次可以一个一个字节存取内存。例如在C语言中使用char *指代“一块内存”,Java中使用byte[]指代一块内存。如下所示。
但那实际上计算机处理器却不是这样认为的。处理器相对比较“懒惰”,它会以2字节,4字节,8字节,16字节甚至32字节来存取内存。例如下图显示了以4字节为单位读写内存的处理器“看待”上述内存的方式。
上述的存取单位的大小称之为内存存取粒度。
下面看一个实例,分别从地址0,和地址1读取4个字节到寄存器。
从程序员的角度来看,读取方式如下图所示。
而2字节存取粒度的处理器的读取方式如下图所示。
可以看出2字节存取粒度的处理器从地址0读取4个字节一共读取2次;从地址1读取4个字节一共读取了3次。由于每次读取的开销是固定的,因此从地址1读取4字节的效率有所下降。
4字节存取粒度的处理器的读取方式如下图所示。
可以看出4字节存取粒度的处理器从地址0读取4个字节一共读取1次;从地址1读取4个字节一共读取了2次。从地址1读取的开销比从地址0读取多了一倍。由此可见内存不对齐对CPU的性能是有影响的。
源码分析
对主要代码进行了注释
保留常用函数,删减部分函数
libavutil–mem.c
/*
* default memory allocator for libavutil
* Copyright (c) 2002 Fabrice Bellard
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* default memory allocator for libavutil
*/
#define _XOPEN_SOURCE 600
#include "config.h"
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#if HAVE_MALLOC_H
#include <malloc.h>
#endif
#include "avassert.h"
#include "avutil.h"
#include "common.h"
#include "dynarray.h"
#include "intreadwrite.h"
#include "mem.h"
#ifdef MALLOC_PREFIX
#define malloc AV_JOIN(MALLOC_PREFIX, malloc)
#define memalign AV_JOIN(MALLOC_PREFIX, memalign)
#define posix_memalign AV_JOIN(MALLOC_PREFIX, posix_memalign)
#define realloc AV_JOIN(MALLOC_PREFIX, realloc)
#define free AV_JOIN(MALLOC_PREFIX, free)
void *malloc(size_t size);
void *memalign(size_t align, size_t size);
int posix_memalign(void **ptr, size_t align, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);
#endif /* MALLOC_PREFIX */
#include "mem_internal.h"
#define ALIGN (HAVE_AVX ? 32 : 16)
/* NOTE: if you want to override these functions with your own
* implementations (not recommended) you have to link libav* as
* dynamic libraries and remove -Wl,-Bsymbolic from the linker flags.
* Note that this will cost performance. */
/**
* size_t 类型定义在cstddef头文件中,该文件是C标准库的头文件stddef.h的C++版。
* 它是一个与机器相关的unsigned类型,其大小足以保证存储内存中对象的大小。
*/
static size_t max_alloc_size= INT_MAX;
/**
* 设置max size
*/
void av_max_alloc(size_t max){
max_alloc_size = max;
}
/**
* 内存分配函数
* 简单的封装了系统函数malloc(),并做了一些错误检查工作
*
* 去掉宏定义,精简后av_malloc如下
*
* void *av_malloc(size_t size)
* {
* void *ptr = NULL;
* // let's disallow possibly ambiguous cases
* if(size>(max_alloc_size-32))
* return NULL;
* ptr=malloc(size);
* if(!ptr&&!size){
* size=1;
* ptr=av_malloc(1);
* }
* return ptr;
* }
*/
void *av_malloc(size_t size)
{
void *ptr = NULL;
/* let's disallow possibly ambiguous cases 让我们禁止可能模棱两可的事件 */
if (size > (max_alloc_size - 32))
return NULL;
#if HAVE_POSIX_MEMALIGN
if (size) // OS X on SDK 10.6 has a broken posix_memalign implementation :
// SDK 10.6上的OS X具有损坏的posix_memalign实现
if (posix_memalign(&ptr, ALIGN, size))
ptr = NULL;
#elif HAVE_ALIGNED_MALLOC
ptr = _aligned_malloc(size, ALIGN);
#elif HAVE_MEMALIGN
#ifndef __DJGPP__
// 在GNU系统中,malloc或realloc返回的内存块地址都是8的倍数(如果是64位系统,则为16的倍数)。
// 如果你需要更大的粒度,请使用memalign或valloc。这些函数在头文件“stdlib.h”中声明。
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是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。
memset(ptr, FF_MEMORY_POISON, size);
#endif
return ptr;
}
/**
* 内存重新分配函数
* 简单的封装了系统函数realloc(),并做了一些错误检查工作
*
* realloc():realloc() 对 ptr 指向的内存重新分配 size 大小的空间,size 可比原来的大或者小,还可以不变。
* 当 malloc()、calloc() 分配的内存空间不够用时,就可以用 realloc() 来调整已分配的内存。
*
* 去掉宏定义,精简后av_realloc如下
*
* void *av_realloc(void *ptr, size_t size)
* {
* // let's disallow possibly ambiguous cases
* if(size>(max_alloc_size-32))
* return NULL;
* return realloc(ptr,size+!size);
* }
*/
void *av_realloc(void *ptr, size_t size)
{
/* let's disallow possibly ambiguous cases */
if (size > (max_alloc_size - 32))
return NULL;
#if HAVE_ALIGNED_MALLOC
return _aligned_realloc(ptr, size + !size, ALIGN);
#else
return realloc(ptr, size + !size);
#endif
}
/**
* 内存重新分配函数(计算最大内存)
* 把两个size_t相乘,再调用av_realloc进行重新分配
*/
void *av_realloc_f(void *ptr, size_t nelem, size_t elsize)
{
size_t size;
void *r;
/**
* static inline int av_size_mult(size_t a, size_t b, size_t *r)
*
* Multiply two `size_t` values checking for overflow.
*
* @param[in] a,b Operands of multiplication
* @param[out] r Pointer to the result of the operation
* @return 0 on success, AVERROR(EINVAL) on overflow
*
* elsize×nelem,结果返回size;如果相乘的结果超过最大值,则size为最大值
*/
if (av_size_mult(elsize, nelem, &size)) {
av_free(ptr);
return NULL;
}
r = av_realloc(ptr, size);
if (!r)
av_free(ptr);
return r;
}
/**
* 内存重新分配函数(Copy)
* 先将原来内存中的内容拷贝出来,再调用av_realloc重新分配内存,分配完成后将原来的内容拷贝回去
*
* memcpy指的是C和C++使用的内存拷贝函数,函数原型为void *memcpy(void *destin, void *source, unsigned n);
* 函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中,即从源source中拷贝n个字节到目标destin中。
*/
int av_reallocp(void *ptr, size_t size)
{
void *val;
if (!size) {
av_freep(ptr);
return 0;
}
// copy ptr to val
memcpy(&val, ptr, sizeof(val));
val = av_realloc(val, size);
if (!val) {
av_freep(ptr);
return AVERROR(ENOMEM);
}
// copy val back to ptr
memcpy(ptr, &val, sizeof(val));
return 0;
}
...
/**
* 简单的封装了free()
*/
void av_free(void *ptr)
{
#if HAVE_ALIGNED_MALLOC
_aligned_free(ptr);
#else
free(ptr);
#endif
}
/**
* 简单的封装了av_free(),并且在释放内存之后将目标指针设置为NULL
*/
void av_freep(void *arg)
{
void *val;
memcpy(&val, arg, sizeof(val));
memcpy(arg, &(void *){ NULL }, sizeof(val));
av_free(val);
}
/**
* 调用av_malloc,并将分配的内存设为0
*/
void *av_mallocz(size_t size)
{
void *ptr = av_malloc(size);
if (ptr)
memset(ptr, 0, size);
return ptr;
}
/**
* 先计算需要分配的内存大小是否合法,再调用av_mallocz
*/
void *av_calloc(size_t nmemb, size_t size)
{
if (size <= 0 || nmemb >= INT_MAX / size)
return NULL;
return av_mallocz(nmemb * size);
}
...
/**
* deliberately overlapping memcpy implementation
*
* dst destination buffer
* back how many bytes back we start (the initial size of the overlapping window), must be > 0
* cnt number of bytes to copy, must be >= 0
*
* cnt> back有效,这将复制我们刚刚复制的字节,从而创建一个周期长度为back的重复模式。
*/
void av_memcpy_backptr(uint8_t *dst, int back, int cnt)
{
const uint8_t *src = &dst[-back];
if (!back)
return;
if (back == 1) {
// memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。
// memset()函数原型是extern void *memset(void *buffer, int c, int count)
// buffer:为指针或是数组
// c:是赋给buffer的值
// count:是buffer的长度
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;
}
}
...