【malloc原理】 内存分配函数malloc的原理

 

今天在群里有个哥们问malloc是不是分配内存后内存指针的之前的4字节保存分配的内存大小,于是试了一下。

Windows默认进程地址空间为2GB,也就是一个进程最多分配的内存接近2GB,但不可能达到。 不过应该有方法可以突破这个限制,在VC++2010里工程属性设置里有一个项貌似是来设置这个的,如下图:

 

但是我设置了启用后用malloc申请大于等于2GB的内存时还是不成功,这个不知道怎么回事。

接着说malloc的事,看其他文章(http://hi.baidu.com/cuihao0532/item/1f88ae69e4753998c5d2499b)时,里面提到分配内存后该地址的前面应该是一个结构体,这个结构体来保存分配的这段内存的相关信息,当然,内存大小是必须保存下来的,至于是不是结构体中的成员来保存这个也不清楚(我到底要说什么。。。)

继续。。

int *p = (int*)malloc(size);

2GB == 1024 * 1024 * 1024 * 2 == 0x80000000,可见2GB对应为32个二进制位,也就是4字节,

3GB == 0XC0000000, 3GB也是32位==4字节,而4GB为0x100000000,有36个二进制位,也就是需要5字节,那么也就是用来保存这个分配大小的内存尺寸对可以分配的内存大小至关重要! 这一点要知道

在我的实验中,发现分配大于等于2GB一直都没成功,所以就暂且认为在我的环境设置下不可能申请2GB动态内存,那么只关注用来保存分配大小的4字节即可,这是前提!

 

 

-----------说明:下面图上的红字写的“p的地址为****”, 这种说法不正确,应该是p的值是***,即p的指向为****, 下面在文字里做了更正,图上就不更正了-----

--------------------------------------------------------------------------

------------------------------------------------------

先看第一个实验,分配100字节,int类型, 100对应的二进制是0x64,

在这个实验中, p的值为0x00406d80, 即p指向的地址为0x00406d80,堆是往高地址扩展的(栈是往低地址扩展的),为这100字节内存全都初始化为十进制的16,也就是十六进制的0x10,

从0x00406d80往前数16字节发现了100对应的十六进制64!   需要注意的是:100可以用一个字节表示,我们无法知道作为4字节来表示的分配的100字节大小

是从p之前的16字节开始的还是在16字节位置结束的, 虽然我们可以大概知道:高位数字字节在高地址,地位数字字节在低地址,64作为低位字节数字,那么这4字节应该是在

64位置结束的,从而从64开始往高地址的4字节即为保存分配大小的内存单元,  我们还需要验证一下。  

----------------------------------------------------------------------------------------------------------------

看下图来验证

分配291字节内存,对应十六进制为0x123,即0x0123嘛!

p的值为0x266d80,即p指向的内存单元的地址为0x266d80,从p指向的位置往前倒16字节,发现了23和01,通过这个说明了这个大小是

从p倒数16字节到13字节的4字节来保存分配大小的!

 

-----------------------------------------------------------------------------------------------------------

下面再做一个实验

分配了0x7FEFFFFF字节的内存

可以发现在p指向的地址之前16到13字节处,发现了我们分配的大小,内存排列从低地址到高地址为:ff ff ef 7f, 也就是我们分配的尺寸!

 

下面再想一个问题,我们如何用程序读出这个尺寸呢?   看下面代码

1     cout<<"分配了"<<size3<<"字节"<<endl;
2 
3 
4     int *pt = p - 16;          //p之前16字节(注意这里,陷阱!!!)
5     UINT64 allSize = *((UINT64*)(pt)); //陷阱!
6     cout<<"验证一下,分配了"<<allSize<<"字节"<<endl; 

 

看上面的代码,分析一下: 由于p为int类型指针,那么我们就定义一个int  *pt来指向p之前的16字节, 这样做是不是就对了呢?

当然不对,为什么? 因为指针减去一个数字N,减去的不是N字节,而是N * sizeof(指针类型)字节, 在这里指针为int类型,所以减去的实际是16 * sizeof(int) == 64字节,

结果当然不对!  

所以需要修改为  

1 int *pt = p - 16 / sizeof(int);  //减去16字节除以int所占字节数个指针大小

 

那么,这样结果是不是就对了呢?  发现结果还是不对,为什么呢,问题可能出现在下一句上,size3我们是用UINT64类型来定义的,那么是不是也应该用一个UINT64来接收呢?

仔细一想,发现:size3 == 0x7FEFFFFF,size3占64位,即8字节, 我们用UINT64去接收*((UINT64*)(pt))时,转换为UINT64*再解引用是从pt开始往高地址数sizeof(UINT64)字节得到的值, 而用来保存分配尺寸的内存仅仅为4字节!!!   所以错出在了这里,我们应该从p之前16字节处开始找4字节的值来接收,所以用一个UINT可以接收的范围包括0x7FEFFFFF这个数字,我们试着把代码改成如下:

1     cout<<"分配了"<<size3<<"字节"<<endl;
2 
3 
4     int *pt = p - (16 / sizeof(int));
5     UINT allSize = *((UINT*)(pt));
6     cout<<"验证一下,分配了"<<allSize<<"字节"<<endl; 

 

运行发现:这次取到的allSize和size3相等了。

 

就说到这里,通过上述实验我发现: 细节非常非常重要!    比如指针减去一个数字N并非直接减去N字节, 而是减去N * sizeof(指针类型)字节,  还有一个数字,高位字节在高地址,低位字节在低地址等。

 

至于malloc后,操作系统保存的其他信息今天就暂时不说了。

 

 

 

posted on 2013-04-10 17:59  崔好好  阅读(1045)  评论(0编辑  收藏  举报

导航