• free() 是如何释放不同内存区块大小的指针?


    最初是在知乎上看到这个问题的C++ delete[] 是如何知道数组大小的?,我也挺好奇,所以就作了一番工作。

    申请内存时,指针所指向区块的大小这一信息,其实就记录在该指针的周围
    看下面这段代码:

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<malloc.h>
     4 #include<assert.h>
     5 #include<ctime>
     6 using namespace std;
     7 
     8 #define size 16
     9 
    10 int main(void)
    11 {
    12     void * p = NULL;
    13     srand(time(0));
    14     int a = 10;
    15     while (a--)
    16     {
    17         int n = rand() % 10000;
    18         p = malloc(n);
    19         size_t w = *((size_t*)((char*)p - size));
    20         cout << "w=" << w << endl;
    21         cout << "n=" << n << endl;
    22         assert(w == n);
    23         free(p);
    24     }
    25     return 0;
    26 }

    (注:如果是X86的CPU,请将 size 改为 8)

    你会发现 w 和 n 始终是一致的,,这样其实不是巧合,来看 M$ 编译器 vc include 目录下 malloc.h这一头文件 中 184 到 209 行的代码:

     1 //这儿是根据不同的硬件平台的宏定义
     2 #if defined (_M_IX86)
     3 #define _ALLOCA_S_MARKER_SIZE   8
     4 #elif defined (_M_X64)
     5 #define _ALLOCA_S_MARKER_SIZE   16
     6 #elif defined (_M_ARM)
     7 #define _ALLOCA_S_MARKER_SIZE   8
     8 #elif !defined (RC_INVOKED)
     9 #error Unsupported target platform.
    10 #endif  /* !defined (RC_INVOKED) */
    11 
    12 _STATIC_ASSERT(sizeof(unsigned int) <= _ALLOCA_S_MARKER_SIZE);
    13 
    14 #if !defined (__midl) && !defined (RC_INVOKED)
    15 #pragma warning(push)
    16 #pragma warning(disable:6540)
    17 __inline void *_MarkAllocaS(_Out_opt_ __crt_typefix(unsigned int*) void *_Ptr, unsigned int _Marker)
    18 {
    19     if (_Ptr)
    20     {
    21         *((unsigned int*)_Ptr) = _Marker;
    22  //
    23         _Ptr = (char*)_Ptr + _ALLOCA_S_MARKER_SIZE;
    24  //最后返回给调用者的指针,是原始指针偏移了_ALLOCA_S_MARKER_SIZE的新指针,这也是刚才我将指针向后偏移,就能得到该指针所指向内存区块的大小的原因。
    25     }
    26     return _Ptr;
    27 }

    再来看看在 M$ 编译器中它是如何释放的,同样在 mallloc.h 文件249行到274行:

     1 /* _freea must be in the header so that its allocator matches _malloca */
     2 #if !defined (__midl) && !defined (RC_INVOKED)
     3 #if !(defined (_DEBUG) && defined (_CRTDBG_MAP_ALLOC))
     4 #undef _freea
     5 __pragma(warning(push))
     6 __pragma(warning(disable: 6014))
     7 _CRTNOALIAS __inline void __CRTDECL _freea(_Pre_maybenull_ _Post_invalid_ void * _Memory)
     8 {
     9     unsigned int _Marker;
    10     if (_Memory)
    11     {
    12         _Memory = (char*)_Memory - _ALLOCA_S_MARKER_SIZE;
    13 //获得原始指针
    14         _Marker = *(unsigned int *)_Memory;//得到指针所指区块的大小
    15         if (_Marker == _ALLOCA_S_HEAP_MARKER)
    16         {
    17             free(_Memory);
    18         }
    19 #if defined (_ASSERTE)
    20         else if (_Marker != _ALLOCA_S_STACK_MARKER)
    21         {
    22             #pragma warning(suppress: 4548) /* expression before comma has no effect */
    23             _ASSERTE(("Corrupted pointer passed to _freea", 0));
    24         }
    25 #endif  /* defined (_ASSERTE) */
    26     }
    27 }

    再来看看 SGI STL标准库源码 stl_alloc.h 文件209 行到 246行 debug_alloc类模板的设计:

     1 // Allocator adaptor to check size arguments for debugging.
     2 // Reports errors using assert.  Checking can be disabled with
     3 // NDEBUG, but it's far better to just use the underlying allocator
     4 // instead when no checking is desired.
     5 // There is some evidence that this can confuse Purify.
     6 template <class _Alloc>
     7 class debug_alloc {
     8 
     9 private:
    10 
    11   enum {_S_extra = 8};  // Size of space used to store size.  Note
    12                         // that this must be large enough to preserve
    13                         // alignment.
    14 
    15                         //这儿就像它所说的那样
    16 public:
    17 
    18   static void* allocate(size_t __n)
    19   {
    20     //
    21 这里实际申请的内存大小要多 8 个字节
    22     char* __result = (char*)_Alloc::allocate(__n + (int) _S_extra);
    23     *(size_t*)__result = __n;//前 4 个字节用于存储区块大小,可以看到,它预留了4个字节的空白区,具体原由 还望大牛能指出,==。
    24     return __result + (int) _S_extra;//最后返回相对于原始指针偏移8个字节的新指针
    25   }
    26 
    27   static void deallocate(void* __p, size_t __n)
    28   {
    29     char* __real_p = (char*)__p - (int) _S_extra;//获得原始指针
    30     assert(*(size_t*)__real_p == __n);//这里增加了一个断言,防止析构了被破坏的指针
    31     _Alloc::deallocate(__real_p, __n + (int) _S_extra);
    32   }
    33 
    34   static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz)
    35   {
    36     char* __real_p = (char*)__p - (int) _S_extra;
    37     assert(*(size_t*)__real_p == __old_sz);
    38     char* __result = (char*)
    39       _Alloc::reallocate(__real_p, __old_sz + (int) _S_extra,
    40                                    __new_sz + (int) _S_extra);
    41     *(size_t*)__result = __new_sz;
    42     return __result + (int) _S_extra;
    43   }
    44 
    45 };

    再来看看 gcc 下,其实也有类似的设计:

     1 #if(defined(_X86_) && !defined(__x86_64))
     2 #define _ALLOCA_S_MARKER_SIZE 8
     3 #elif defined(__ia64__) || defined(__x86_64)
     4 #define _ALLOCA_S_MARKER_SIZE 16
     5 #endif
     6 
     7 #if !defined(RC_INVOKED)
     8   static __inline void *_MarkAllocaS(void *_Ptr,unsigned int _Marker) {
     9     if(_Ptr) {
    10       *((unsigned int*)_Ptr) = _Marker;
    11       _Ptr = (char*)_Ptr + _ALLOCA_S_MARKER_SIZE;
    12     }
    13     return _Ptr;
    14   }
    15 #endif
     1 #ifndef RC_INVOKED
     2 #undef _freea
     3   static __inline void __cdecl _freea(void *_Memory) {
     4     unsigned int _Marker;
     5     if(_Memory) {
     6       _Memory = (char*)_Memory - _ALLOCA_S_MARKER_SIZE;
     7       _Marker = *(unsigned int *)_Memory;
     8       if(_Marker==_ALLOCA_S_HEAP_MARKER) {
     9     free(_Memory);
    10       }
    11 #ifdef _ASSERTE
    12       else if(_Marker!=_ALLOCA_S_STACK_MARKER) {
    13     _ASSERTE(("Corrupted pointer passed to _freea",0));
    14       }
    15 #endif
    16     }
    17   }
    18 #endif /* RC_INVOKED */

    其实,很多在实际写代码中困惑我们的问题,都可以通过 阅读相关源代码来得到 答案。

    所以,经常阅读那些开源代码,还是相当有好处的 :)

  • 相关阅读:
    使用 Visual Studio 2022 开发 Linux C++ 应用程序
    CSDN博客 新版主页与旧版主页切换
    C++ 静态局部变量 全局变量
    静态构造函数
    使用jquery的ajax功能post数据
    图片的禁止缓存与预缓存
    cookie
    asp.net中使用excel类导出Excel文件,并导出到web客户端中遇到的问题
    xmlHttpRequest对象的使用
    html静态文件提交到aspx页面处理
  • 原文地址:https://www.cnblogs.com/xiahualin/p/4058002.html
Copyright © 2020-2023  润新知