• FFmpeg中内存分配和释放相关的源码:av_malloc函数、av_mallocz函数、av_free函数和av_freep函数分析


    一、av_malloc函数分析

    (一)av_malloc函数的声明

    av_malloc函数的声明放在在FFmpeg源码(本文演示用的FFmpeg源码版本为5.0.3,该ffmpeg在CentOS 7.5上通过10.2.1版本的gcc编译)的头文件libavutil/mem.h中:

    1. /**
    2. * Allocate a memory block with alignment suitable for all memory accesses
    3. * (including vectors if available on the CPU).
    4. *
    5. * @param size Size in bytes for the memory block to be allocated
    6. * @return Pointer to the allocated block, or `NULL` if the block cannot
    7. * be allocated
    8. * @see av_mallocz()
    9. */
    10. void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);

    根据注释,可以了解到该函数作用是分配一个适合所有内存访问的内存块(包括动态数组,如果CPU上可用)。形参size:要分配的内存块的字节大小。返回值:指向分配块的指针;如果无法申请内存,则返回NULL。

    宏定义av_malloc_attrib也被定义在libavutil/mem.h中:

    1. /**
    2. * @def av_malloc_attrib
    3. * Function attribute denoting a malloc-like function.
    4. *
    5. */
    6. #if AV_GCC_VERSION_AT_LEAST(3,1)
    7. #define av_malloc_attrib __attribute__((__malloc__))
    8. #else
    9. #define av_malloc_attrib
    10. #endif

    这里用attribute指定__malloc__属性,表示这样标记的函数(av_malloc函数)返回的块不得包含任何指向其他对象的指针。这样做的目的是帮助编译器估计哪些指针可能指向同一个对象:该属性告诉GCC,它不必担心函数返回的对象可能包含指向它正在跟踪的其他对象的指针。具体可以参考:GCC: __attribute__((malloc))

    宏定义av_malloc_attrib也被定义在libavutil/mem.h中:

    1. /**
    2. * @def av_alloc_size(...)
    3. * Function attribute used on a function that allocates memory, whose size is
    4. * given by the specified parameter(s).
    5. *
    6. * @code{.c}
    7. * void *av_malloc(size_t size) av_alloc_size(1);
    8. * void *av_calloc(size_t nmemb, size_t size) av_alloc_size(1, 2);
    9. * @endcode
    10. *
    11. * @param ... One or two parameter indexes, separated by a comma
    12. *
    13. */
    14. #if AV_GCC_VERSION_AT_LEAST(4,3)
    15. #define av_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__)))
    16. #else
    17. #define av_alloc_size(...)
    18. #endif

    这里用attribute指定alloc_size属性,用于告诉编译器函数返回值指向的内存,其大小是由alloc_size中的参数指定,主要用于提高__builtin_object_size的正确性。具体参考:GCC __atrribute__

    然后宏定义用到了变参__VA_ARGS__,可以参考:C 语言 define 变参__VA_ARGS__使用

    所以函数声明void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1) 等价于:

    void *av_malloc(size_t size) __attribute__((__malloc__)) __attribute__((alloc_size(1)))

    简单来讲,__attribute__只是让代码优化得更好而已,我们可以忽略。

    (二)av_malloc函数的实现原理

    av_malloc函数定义在libavutil/mem.c中:

    1. #define HAVE_POSIX_MEMALIGN 1
    2. #define HAVE_ALIGNED_MALLOC 0
    3. #define HAVE_MEMALIGN 1
    4. #define HAVE_AVX512 0
    5. #define HAVE_AVX 0
    6. #define CONFIG_MEMORY_POISONING 0
    7. #define ALIGN (HAVE_AVX512 ? 64 : (HAVE_AVX ? 32 : 16))
    8. static atomic_size_t max_alloc_size = ATOMIC_VAR_INIT(INT_MAX);
    9. void *av_malloc(size_t size)
    10. {
    11. void *ptr = NULL;
    12. if (size > atomic_load_explicit(&max_alloc_size, memory_order_relaxed))
    13. return NULL;
    14. #if HAVE_POSIX_MEMALIGN
    15. if (size) //OS X on SDK 10.6 has a broken posix_memalign implementation
    16. if (posix_memalign(&ptr, ALIGN, size))
    17. ptr = NULL;
    18. #elif HAVE_ALIGNED_MALLOC
    19. ptr = _aligned_malloc(size, ALIGN);
    20. #elif HAVE_MEMALIGN
    21. #ifndef __DJGPP__
    22. ptr = memalign(ALIGN, size);
    23. #else
    24. ptr = memalign(size, ALIGN);
    25. #endif
    26. /* Why 64?
    27. * Indeed, we should align it:
    28. * on 4 for 386
    29. * on 16 for 486
    30. * on 32 for 586, PPro - K6-III
    31. * on 64 for K7 (maybe for P3 too).
    32. * Because L1 and L2 caches are aligned on those values.
    33. * But I don't want to code such logic here!
    34. */
    35. /* Why 32?
    36. * For AVX ASM. SSE / NEON needs only 16.
    37. * Why not larger? Because I did not see a difference in benchmarks ...
    38. */
    39. /* benchmarks with P3
    40. * memalign(64) + 1 3071, 3051, 3032
    41. * memalign(64) + 2 3051, 3032, 3041
    42. * memalign(64) + 4 2911, 2896, 2915
    43. * memalign(64) + 8 2545, 2554, 2550
    44. * memalign(64) + 16 2543, 2572, 2563
    45. * memalign(64) + 32 2546, 2545, 2571
    46. * memalign(64) + 64 2570, 2533, 2558
    47. *
    48. * BTW, malloc seems to do 8-byte alignment by default here.
    49. */
    50. #else
    51. ptr = malloc(size);
    52. #endif
    53. if(!ptr && !size) {
    54. size = 1;
    55. ptr= av_malloc(1);
    56. }
    57. #if CONFIG_MEMORY_POISONING
    58. if (ptr)
    59. memset(ptr, FF_MEMORY_POISON, size);
    60. #endif
    61. return ptr;
    62. }

    去掉一大堆其它东西,av_malloc函数的核心实现就是:

    1. void *av_malloc(size_t size)
    2. {
    3. //...
    4. void *ptr = NULL;
    5. if (posix_memalign(&ptr, ALIGN, size))
    6. {
    7. ptr = NULL;
    8. }
    9. //...
    10. return ptr;
    11. }

    可以看到其本质就是调用了posix_memalign函数,该函数是Linux下内存对齐的函数,其作用是分配size大小的字节,并将分配的内存地址存放在ptr中。分配的内存的地址将是ALIGN的倍数,且必须是2的幂次方和sizeof(void*)的倍数。如果size为0,则函数返回NULL或一个唯一的指针值,以便可以成功传递给free函数。如果分配成功返回0该函数跟malloc函数相近。posix_memalign函数跟malloc函数的区别是: malloc函数总是返回8字节对齐的内存地址,在64bits上是16字节对齐;而对于更大的边界,例如页面,需要动态的对齐的时候就可以选择posix_memalign函数。具体可以参考:posix_memalign(3) — Linux manual page

    总结:av_malloc函数作用是分配一个适合所有内存访问的内存块,形参size为 要分配的内存块的字节大小。其底层实现就是调用了posix_memalign函数。

    二、av_mallocz函数分析

    (一)av_mallocz函数的声明

    av_mallocz函数的声明放在在FFmpeg源码的头文件libavutil/mem.h中:

    1. /**
    2. * Allocate a memory block with alignment suitable for all memory accesses
    3. * (including vectors if available on the CPU) and zero all the bytes of the
    4. * block.
    5. *
    6. * @param size Size in bytes for the memory block to be allocated
    7. * @return Pointer to the allocated block, or `NULL` if it cannot be allocated
    8. * @see av_malloc()
    9. */
    10. void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);

    根据注释,可以了解到该函数作用是分配一个适合所有内存访问的内存块
    (包括CPU上可用的动态数组)并将块的所有字节归零。形参size:要分配的内存块的字节大小。
    返回值指向已分配块的指针,如果不能分配,则为NULL。

    (二)av_mallocz函数的实现原理

    av_mallocz函数定义在libavutil/mem.c中:

    1. void *av_mallocz(size_t size)
    2. {
    3. void *ptr = av_malloc(size);
    4. if (ptr)
    5. memset(ptr, 0, size);
    6. return ptr;
    7. }

    可以看到其核心就是调用了av_malloc函数分配内存块,随后调用memset函数将该内存块的所有字节清零。因为av_mallocz函数会将内存块清零(相当于对内存块进行一个初始化操作)。所以在FFmpeg源码中一般使用av_mallocz,而不会直接使用av_malloc函数。

    三、av_free函数分析

    (一)av_free函数的声明

    av_free函数的声明放在在FFmpeg源码的头文件libavutil/mem.h中:

    1. /**
    2. * Free a memory block which has been allocated with a function of av_malloc()
    3. * or av_realloc() family.
    4. *
    5. * @param ptr Pointer to the memory block which should be freed.
    6. *
    7. * @note `ptr = NULL` is explicitly allowed.
    8. * @note It is recommended that you use av_freep() instead, to prevent leaving
    9. * behind dangling pointers.
    10. * @see av_freep()
    11. */
    12. void av_free(void *ptr);

    根据注释,可以了解到该函数作用是释放av_malloc()/av_mallocz()函数分配的内存块。形参ptr:指向应该释放的内存块的指针。
     

    (二)av_free函数的实现原理

    av_free函数定义在libavutil/mem.c中:

    1. void av_free(void *ptr)
    2. {
    3. #if HAVE_ALIGNED_MALLOC
    4. _aligned_free(ptr);
    5. #else
    6. free(ptr);
    7. #endif
    8. }

    可以看到其核心就是调用free函数释放空间。

    四、av_freep函数分析

    (一)av_freep函数的声明

    av_freep函数的声明放在在FFmpeg源码的头文件libavutil/mem.h中:

    1. /**
    2. * Free a memory block which has been allocated with a function of av_malloc()
    3. * or av_realloc() family, and set the pointer pointing to it to `NULL`.
    4. *
    5. * @code{.c}
    6. * uint8_t *buf = av_malloc(16);
    7. * av_free(buf);
    8. * // buf now contains a dangling pointer to freed memory, and accidental
    9. * // dereference of buf will result in a use-after-free, which may be a
    10. * // security risk.
    11. *
    12. * uint8_t *buf = av_malloc(16);
    13. * av_freep(&buf);
    14. * // buf is now NULL, and accidental dereference will only result in a
    15. * // NULL-pointer dereference.
    16. * @endcode
    17. *
    18. * @param ptr Pointer to the pointer to the memory block which should be freed
    19. * @note `*ptr = NULL` is safe and leads to no action.
    20. * @see av_free()
    21. */
    22. void av_freep(void *ptr);

    根据注释,可以了解到该函数作用是释放av_malloc()/av_mallocz()函数分配的内存块,并设置指向它的指针为NULL 。

    (二)av_freep函数的实现原理

    av_freep函数定义在libavutil/mem.c中:

    1. void av_freep(void *arg)
    2. {
    3. void *val;
    4. memcpy(&val, arg, sizeof(val));
    5. memcpy(arg, &(void *){ NULL }, sizeof(val));
    6. av_free(val);
    7. }

    可以看到其核心就是调用了av_free函数释放空间。然后av_freep函数可以避免仅使用av_free函数释放空间后出现的悬挂指针,避免安全风险。所以av_freep函数比仅使用av_free函数更安全。

    五、编写测试例子,来理解av_mallocz函数和av_freep函数的使用

    通过上述的讲解,相信大家已经了解了FFmpeg源码中这几个内存相关的函数,也理解了FFmpeg 源码中C语言的设计艺术。就拿FFmpeg的内存相关的函数来讲,其设计艺术在于:av_mallocz函数申请、分配内存后,还会对该内存块进行初始化清零的操作;av_freep函数在释放内存块空间后会把指针指向NULL。我们可以把av_mallocz函数和av_freep函数从FFmpeg源码中抽取出来,移植到我们自己的C语言代码中使用。这样在不依赖FFmpeg库文件的情况下,我们也能使用FFmpeg里面的函数。这就是最简单的对FFmpeg裁剪。

    编写测试例子main.c,在CentOS 7.5上通过10.2.1版本的gcc可以成功编译 :

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #define HAVE_POSIX_MEMALIGN 1
    8. #define HAVE_ALIGNED_MALLOC 0
    9. #define HAVE_MEMALIGN 1
    10. #define HAVE_AVX512 0
    11. #define HAVE_AVX 0
    12. #define CONFIG_MEMORY_POISONING 0
    13. #define ALIGN (HAVE_AVX512 ? 64 : (HAVE_AVX ? 32 : 16))
    14. static atomic_size_t max_alloc_size = ATOMIC_VAR_INIT(INT_MAX);
    15. #ifdef __GNUC__
    16. # define AV_GCC_VERSION_AT_LEAST(x,y) (__GNUC__ > (x) || __GNUC__ == (x) && __GNUC_MINOR__ >= (y))
    17. # define AV_GCC_VERSION_AT_MOST(x,y) (__GNUC__ < (x) || __GNUC__ == (x) && __GNUC_MINOR__ <= (y))
    18. #else
    19. # define AV_GCC_VERSION_AT_LEAST(x,y) 0
    20. # define AV_GCC_VERSION_AT_MOST(x,y) 0
    21. #endif
    22. #if AV_GCC_VERSION_AT_LEAST(3,1)
    23. #define av_malloc_attrib __attribute__((__malloc__))
    24. #else
    25. #define av_malloc_attrib
    26. #endif
    27. #if AV_GCC_VERSION_AT_LEAST(4,3)
    28. #define av_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__)))
    29. #else
    30. #define av_alloc_size(...)
    31. #endif
    32. void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);
    33. void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);
    34. void av_free(void *ptr);
    35. void av_freep(void *ptr);
    36. void *av_malloc(size_t size)
    37. {
    38. void *ptr = NULL;
    39. if (size > atomic_load_explicit(&max_alloc_size, memory_order_relaxed))
    40. return NULL;
    41. #if HAVE_POSIX_MEMALIGN
    42. if (size) //OS X on SDK 10.6 has a broken posix_memalign implementation
    43. if (posix_memalign(&ptr, ALIGN, size))
    44. ptr = NULL;
    45. #elif HAVE_ALIGNED_MALLOC
    46. ptr = _aligned_malloc(size, ALIGN);
    47. #elif HAVE_MEMALIGN
    48. #ifndef __DJGPP__
    49. ptr = memalign(ALIGN, size);
    50. #else
    51. ptr = memalign(size, ALIGN);
    52. #endif
    53. /* Why 64?
    54. * Indeed, we should align it:
    55. * on 4 for 386
    56. * on 16 for 486
    57. * on 32 for 586, PPro - K6-III
    58. * on 64 for K7 (maybe for P3 too).
    59. * Because L1 and L2 caches are aligned on those values.
    60. * But I don't want to code such logic here!
    61. */
    62. /* Why 32?
    63. * For AVX ASM. SSE / NEON needs only 16.
    64. * Why not larger? Because I did not see a difference in benchmarks ...
    65. */
    66. /* benchmarks with P3
    67. * memalign(64) + 1 3071, 3051, 3032
    68. * memalign(64) + 2 3051, 3032, 3041
    69. * memalign(64) + 4 2911, 2896, 2915
    70. * memalign(64) + 8 2545, 2554, 2550
    71. * memalign(64) + 16 2543, 2572, 2563
    72. * memalign(64) + 32 2546, 2545, 2571
    73. * memalign(64) + 64 2570, 2533, 2558
    74. *
    75. * BTW, malloc seems to do 8-byte alignment by default here.
    76. */
    77. #else
    78. ptr = malloc(size);
    79. #endif
    80. if(!ptr && !size) {
    81. size = 1;
    82. ptr= av_malloc(1);
    83. }
    84. #if CONFIG_MEMORY_POISONING
    85. if (ptr)
    86. memset(ptr, FF_MEMORY_POISON, size);
    87. #endif
    88. return ptr;
    89. }
    90. void *av_mallocz(size_t size)
    91. {
    92. void *ptr = av_malloc(size);
    93. if (ptr)
    94. memset(ptr, 0, size);
    95. return ptr;
    96. }
    97. void av_free(void *ptr)
    98. {
    99. #if HAVE_ALIGNED_MALLOC
    100. _aligned_free(ptr);
    101. #else
    102. free(ptr);
    103. #endif
    104. }
    105. void av_freep(void *arg)
    106. {
    107. void *val;
    108. memcpy(&val, arg, sizeof(val));
    109. memcpy(arg, &(void *){ NULL }, sizeof(val));
    110. av_free(val);
    111. }
    112. int main()
    113. {
    114. uint8_t *pBuf = av_mallocz(128);
    115. if(pBuf)
    116. {
    117. strcpy(pBuf, "hello world");
    118. printf("%s\n", pBuf);
    119. av_freep(&pBuf);
    120. if(!pBuf)
    121. {
    122. printf("pBuf is free\n");
    123. }
    124. }
    125. return 0;
    126. }

    使用gcc编译,运行,输出如下:

    六、参考文章

    FFmpeg5.0源码阅读——内存分配和释放

  • 相关阅读:
    Verilog实现半整数分频,3.5分频电路,可推广至N.5分频
    香港写字楼等级如何划分?从3A到C级一文讲明白
    黑马程序员Java实战项目--- ATM系统
    最大路径和——给定一个二叉树的头节点head,路径规定不同问题下的解法
    信钰证券:首板第二天买入技巧?
    Linux I2C tools调试工具
    基于Streamlit的YOLOv5ToX模型转换工具(适用YOLOv5训练出来的模型转化为任何格式)
    NET 8 预览版 2 亮点是Blazor
    LVS集群
    docker安装、设置非sudo执行、卸载
  • 原文地址:https://blog.csdn.net/u014552102/article/details/139737234