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

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

 #include<cstdio>
#include<iostream>
#include<malloc.h>
#include<assert.h>
#include<ctime>
using namespace std; #define size 16 int main(void)
{
void * p = NULL;
srand(time());
int a = ;
while (a--)
{
int n = rand() % ;
p = malloc(n);
size_t w = *((size_t*)((char*)p - size));
cout << "w=" << w << endl;
cout << "n=" << n << endl;
assert(w == n);
free(p);
}
return ;
}

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

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

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

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

 /* _freea must be in the header so that its allocator matches _malloca */
#if !defined (__midl) && !defined (RC_INVOKED)
#if !(defined (_DEBUG) && defined (_CRTDBG_MAP_ALLOC))
#undef _freea
__pragma(warning(push))
__pragma(warning(disable: ))
_CRTNOALIAS __inline void __CRTDECL _freea(_Pre_maybenull_ _Post_invalid_ void * _Memory)
{
unsigned int _Marker;
if (_Memory)
{
_Memory = (char*)_Memory - _ALLOCA_S_MARKER_SIZE;
//获得原始指针
_Marker = *(unsigned int *)_Memory;//得到指针所指区块的大小
if (_Marker == _ALLOCA_S_HEAP_MARKER)
{
free(_Memory);
}
#if defined (_ASSERTE)
else if (_Marker != _ALLOCA_S_STACK_MARKER)
{
#pragma warning(suppress: 4548) /* expression before comma has no effect */
_ASSERTE(("Corrupted pointer passed to _freea", ));
}
#endif /* defined (_ASSERTE) */
}
}

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

 // Allocator adaptor to check size arguments for debugging.
// Reports errors using assert. Checking can be disabled with
// NDEBUG, but it's far better to just use the underlying allocator
// instead when no checking is desired.
// There is some evidence that this can confuse Purify.
template <class _Alloc>
class debug_alloc { private: enum {_S_extra = }; // Size of space used to store size. Note
// that this must be large enough to preserve
// alignment. //这儿就像它所说的那样
public: static void* allocate(size_t __n)
{
//
这里实际申请的内存大小要多 个字节
char* __result = (char*)_Alloc::allocate(__n + (int) _S_extra);
*(size_t*)__result = __n;//前 4 个字节用于存储区块大小,可以看到,它预留了4个字节的空白区,具体原由 还望大牛能指出,==。
return __result + (int) _S_extra;//最后返回相对于原始指针偏移8个字节的新指针
} static void deallocate(void* __p, size_t __n)
{
char* __real_p = (char*)__p - (int) _S_extra;//获得原始指针
assert(*(size_t*)__real_p == __n);//这里增加了一个断言,防止析构了被破坏的指针
_Alloc::deallocate(__real_p, __n + (int) _S_extra);
} static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz)
{
char* __real_p = (char*)__p - (int) _S_extra;
assert(*(size_t*)__real_p == __old_sz);
char* __result = (char*)
_Alloc::reallocate(__real_p, __old_sz + (int) _S_extra,
__new_sz + (int) _S_extra);
*(size_t*)__result = __new_sz;
return __result + (int) _S_extra;
} };

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

 #if(defined(_X86_) && !defined(__x86_64))
#define _ALLOCA_S_MARKER_SIZE 8
#elif defined(__ia64__) || defined(__x86_64)
#define _ALLOCA_S_MARKER_SIZE 16
#endif #if !defined(RC_INVOKED)
static __inline void *_MarkAllocaS(void *_Ptr,unsigned int _Marker) {
if(_Ptr) {
*((unsigned int*)_Ptr) = _Marker;
_Ptr = (char*)_Ptr + _ALLOCA_S_MARKER_SIZE;
}
return _Ptr;
}
#endif
 #ifndef RC_INVOKED
#undef _freea
static __inline void __cdecl _freea(void *_Memory) {
unsigned int _Marker;
if(_Memory) {
_Memory = (char*)_Memory - _ALLOCA_S_MARKER_SIZE;
_Marker = *(unsigned int *)_Memory;
if(_Marker==_ALLOCA_S_HEAP_MARKER) {
free(_Memory);
}
#ifdef _ASSERTE
else if(_Marker!=_ALLOCA_S_STACK_MARKER) {
_ASSERTE(("Corrupted pointer passed to _freea",));
}
#endif
}
}
#endif /* RC_INVOKED */

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

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

free() 是如何释放不同内存区块大小的指针?的更多相关文章

  1. 用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产 生“野指针”

    用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产 生“野指针”. #include <iostream> using namespace std; /* ...

  2. C语言中free函数是如何确定要释放多少内存空间的

    本文链接:http://www.cnblogs.com/xxNote/p/4009359.html 今天看书的时候看到free函数释放动态申请的内存时只需要把内存块的首地址传过去就行了,显然仅仅依靠首 ...

  3. 手工释放linux内存——/proc/sys/vm/drop_caches

    --手工释放linux内存——/proc/sys/vm/drop_caches 总有很多朋友对于Linux的内存管理有疑问,之前一篇日志似乎也没能清除大家的疑虑.而在新版核心中,似乎对这个问题提供了新 ...

  4. 如何计算Java对象所占内存的大小

    [ 简单总结: 随便一个java项目,引入jar包: lucene-core-4.0.0.jar 如果是 maven项目,直接用如下依赖: <dependency> <groupId ...

  5. linux如何手动释放linux内存

    当在Linux下频繁存取文件后,物理内存会很快被用光,当程序结束后,内存不会被正常释放,而是一直作为caching.这个问题,貌似有不少人在问,不过都没有看到有什么很好解决的办法.那么我来谈谈这个问题 ...

  6. Android中App可分配内存的大小

    现在真实测试结果: 1,为了搞清楚每个应用程序在Android系统中最多可分配多少内存空间,我们使用了真机进行测试,测试机型为魅族MX4 Pro,3G内存. 测试方法是直接申请一块较大的内存空间,看应 ...

  7. free()后内存不释放问题 - 内存缓冲池技术(转)

    起因 下面这段代码执行后,内存有增无减,增加了200M,iOS平台200M不能接受了 // STL 集合类 void test1() { list<int> mList; for (int ...

  8. Android中App可分配内存的大小(转)

    转自:http://blog.csdn.net/u011506413/article/details/50965435 现在真实测试结果: 1,为了搞清楚每个应用程序在Android系统中最多可分配多 ...

  9. 释放linux内存中的cache缓存

    echo 3 > /proc/sys/vm/drop_caches 记一次 经常用  exp 导出oracle全量数据库,发现linux内存一直在减小没有释放,即使 oracle重启也不行,只有 ...

随机推荐

  1. lecode Interleaving String

    这个问题,前面思考过,当时就是用搜索的方法,此处又遇到一次,发现自己理解的太浅了 Given s1, s2, s3, find whether s3 is formed by the interlea ...

  2. POJ 3186Treats for the Cows (区间DP)

    详见代码 #include <stdio.h> #include <algorithm> #include <string.h> using namespace s ...

  3. 安卓开发无法识别手机原因之一:手机SDK比工程要求的最小SDK低

      创建工程时,设置限制的最小SDK:   如果你手机的内核版本比你工程的设置的最小SDK低,则不显示   来自为知笔记(Wiz)

  4. 删除用不到的linux内核

    方法1 sudo aptitude purge ~ilinux-image-.*\(\!`uname -r`\) 方法2 sudo apt-get autoremove 方法3 uname -a #使 ...

  5. 深入理解 Spring 事务原理

    本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一.事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供 ...

  6. Linux操作系统的LILO详解

    LILO是一个在Linux环境编写的Boot Loader程序(所以安装和配置它都要在Linux下).它的主要功能就是引导Linux操作系统的启动.但是它不仅可以引导Linux,它还可以引导其他操作系 ...

  7. 【转载】怎么理解Condition

    注:本文主要介绍了Condition和ReentrantLock工具实现独占锁的部分代码原理,Condition能在线程之间协调通信主要是AbstractQueuedSynchronizer和cond ...

  8. Windows版 mysql 5.7.16安装

    MySQL 5.7版本安装教程-踩坑总结 下载 MySQL下载地址选择下载64位(看自己电脑是32位还是64位) 点击下载之后,它会让你登录,没有Oracle账户,跟着它的步骤注册一个就好了. 安装 ...

  9. Git 版本控制工具(学习笔记)

    GIT(分布式) 一.Git 初始版本控制工具 1. 安装Git Ubuntu系统下,打开shell界面,输入: sudo apt-get install git-core  之后回车输入密码,即可完 ...

  10. eclipse中java项目的build path详解(转载)

    BuildPath中只支持加入jar文件,具体方法如下:在eclips里在工程名上右键->build path->contigure bud path->java build pat ...