FFmpeg源碼(二)開疆拓土——mem.c

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;
    }
}

...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章