C动态分配的实际大小
1堆内存
假设从《The C Programming Language》中推测正确,从未经动态分配的堆内存呈现上图形式。不连续的堆内存以“链”的形式联系:Heap1 -> Heap2 ->Heap3 ->Heap4->Heap1。笔记将构成“堆链”的每个堆内存(如Heap1)称为“堆块”。malloc()/free()将每个堆块看作由两部分构成:“Header”和“可用堆内存”。在Header中包含了“指向下一个堆内存块的指针”、“本堆块的大小”。这样malloc()/free()就能更好地管理堆。
2 堆内存分配
[1] mallco()分配机制
根据C中malloc(n)函数动态分配堆的机制:分配堆内存的时候就依序由低到高的地址搜索“堆链”中的堆块,搜索到“可用堆内存”满足n的堆块(如Heap1)为止。若Heap1的“可用堆内存”刚好满足n,则将Heap1从“堆链”中删除,同时重新组织各堆块形成新的“堆链”;若Heap1的“可用堆内存”大小大于n,则将malloc(n)申请到的“Header” + "可用堆内存"部分从Heap1中分裂,将剩余的Heap1堆内存块重新加入“堆链”中。经分裂后的堆内存也包含“Header”和“可用堆内存”两部分(如图Figure 2),然后将由malloc()分配得到的“可用堆内存”返回给用户。若某块堆内存空间比较大(如Heap1),能够满足较小内存的多次申请,那么由malloc(n)多次申请的堆内存块都是连续被分配给用户的(因为有Header,所以用户使用的堆地址不连续)。由于Header的构成的内存对齐,C中malloc(n)函数分配的堆内存会大于等于Header + n。
[2] 程序验证
为了测试C语言malloc()函数所分配堆的实际大小(Header 大小和内存对齐),编写了如下宏:#define malloc_no_free(type, type_str) \
{ \
printf("%s\tsizeof(%s)=%d\t", type_str, type_str, sizeof(type));\
char i; \
type *pthis = NULL, *plast = NULL; \
for(i = 0; i < 6; ++i){ \
if( NULL != (pthis = (type *)malloc(sizeof(type))) ){ \
if(plast) \
printf("%d\t", (int)pthis - (int)plast);\
plast = pthis;/*a litte logic to p*/ \
/*free(pthis);*/ \
pthis = NULL; \
} \
} \
printf("\n\n"); \
}
- 用宏写这一段函数式为了在C中能够传递任何数据类型的变量。
- 如果传递给malloc()函数的参数较小,那么malloc()函数是极有可能在图中的Heap1对区域进行多次分配的,如此这段代码就能够测试出malloc()函数分配堆内存的实际大小。
将此宏在主函数中调用:
struct _sc{ char c; };
struct _sic{ int i; char c; };
struct _sip{ int i; struct _sip *p; };
struct _sdc{ double d; char c;};
struct _s17{ char a[17]; };
int main()
{
malloc_no_free(char, "char");
malloc_no_free(int, "int");
malloc_no_free(double, "double");
malloc_no_free(struct _sc, "_sc");
malloc_no_free(struct _sic, "_sic");
malloc_no_free(struct _sip, "_sip");
malloc_no_free(struct _sdc, "_sdc");
malloc_no_free(struct _s17, "_17");
return 0;
}
在codeblocks + GNU GCC compiler下编译通过并运行得到一下结果:
Figure3:程序运行结果
分析运行结果:
(1) 如果将宏中” free(pthis);”的注释去掉,则所有的地址差都为0。这恰好验证了free()堆地址的机制:假设将要被释放的堆内存的地址为p,free(p)将以p为首地址的堆内存块释放到堆链中的某个地址上,且此地址跟p值最邻近,这样就可以保证尽可能的使堆内存以大块的形式存在而不至于让堆内存成为碎片。
(2) 将” free(pthis);”注释
- 各结构体所占内存大小再一次体现了内存对齐。
- 在一大块堆之上用malloc()函数分配内存,真实得到的内存要比实际大。得到的真实内存块的大小为8(实为内存对齐大小值)的整数倍。当数据结构所占内存为16的倍数时,malloc()分配的内存会多出8个字节,这8个字节就是header。(将struct _s17内数组维数改为16倍数时可验证)。
从这里可以看出:Header所占堆内存大小为8字节,堆内存对齐也为8(会看内存对齐就不难观察出来)。
3 动态分配内存对齐
造就实际分配内存大于用户所需原因:
- 每次malloc(n)分配的堆内存由Header + n构成。
- Header造就的内存对齐。
[1] Header
在《C 圣经》的第八章”The UNIX System Interface”的第七节”Example – A Strorage Allocator”中用以下结构体描述了Header:typedef long Align;
union header {
struct {
union header *ptr;
unsigned size;
} s;
Align x;
};
[2] 内存对齐
《C圣经》中想用Align(long)作为内存对齐的值,但由于win32上sizeof(long) = 4,由于sizeof(union header) = sizeof(s) = 8,故而在malloc()返回的堆内存中存储数据的内存对齐数值为8。
4 malloc()分配总结
对于C中的malloc(n)分配,有以下进一步的结论:
- 实际分配的堆内存是Header + n结构。返回给用户的是n部分的首地址。
- 由于内存对齐值8,实际分配的堆内存大于等于sizeof(Header) + n。