2010-09-25 13:39 浅谈内存分配malloc,calloc,realloc

来源:互联网 发布:淘宝卖假货会不会坐牢 编辑:程序博客网 时间:2024/05/22 06:22
realloc  原型:extern void *realloc(void *mem_address, unsigned int newsize);  用法:#include <stdlib.h> 有些编译器需要#include <malloc.h>  功能:改变mem_address所指内存区域的大小为newsize长度。  说明:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。  当内存不再使用时,应使用free()函数将内存块释放。  注意:这里原始内存中的数据还是保持不变的。  举例:  // realloc.c#include <stdio.h>
#include <malloc.h>
main()
{
char *p;
p=(char *)malloc(100);
if(p)
printf("Memory Allocated at: %x",p);
else
printf("Not Enough Memory!\n");
getchar();
p=(char *)realloc(p,256);
if(p)
printf("Memory Reallocated at: %x",p);
else
printf("Not Enough Memory!\n");
free(p);
getchar();
return 0;
}  详细说明及注意要点:  1、如果有足够空间用于扩大mem_address指向的内存块,则分配额外内存,并返回mem_address  这里说的是“扩大”,我们知道,realloc是从堆上分配内存的,当扩大一块内存空间时, realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平。也就是说,如果原先的内存大小后面还有足够的空闲空间用来分配,加上原来的空间大小= newsize。那么就ok。得到的是一块连续的内存。  2、如果原先的内存大小后面没有足够的空闲空间用来分配,那么从堆中另外找一块newsize大小的内存。  并把原来大小内存空间中的内容复制到newsize中。返回新的mem_address指针。(数据被移动了)。  老块被放回堆上。
例如:#include <stdio.h>
#include <malloc.h>
main()
{
char *p,*q;
p = (char * ) malloc (10);
if(p)
printf("Memory Allocated at: %x",p);
else
printf("Not Enough Memory!\n");
getchar();
q=p;
p = (char * ) realloc (p,20000);
if(p)
printf("Memory Reallocated at: %x",p);
else
printf("Not Enough Memory!\n");
free(p);
getchar(); printf("Memory Reallocated at: %x",p);
printf("Memory Reallocated at: %x\n",q);
return 0;
}  在这段程序中我们增加了指针q,用它记录了原来的内存地址p。这段程序可以编译通过,但在执行到A行时,如果原有内存后面没有足够空间将原有空间扩展成一个连续的新大小的话,realloc函数就会以第二种方式分配内存,此时数据发生了移动,那么所记录的原来的内存地址q所指向的内存空间实际上已经放回到堆上了!这样就会产生q指针的指针悬挂,如果再用q指针进行操作就可能发生意想不到的问题。所以在应用realloc函数是应当格外注意这种情况。
从最后两个printf可以看出,free之后,p,q仍然指向原地址,而不是NULL,因为free表示要翻译p指向的内存空间。但不改变p的值。free(p)执行后,p指向的空间被释放了。但p本身的值并没有改变. 一般要求,指针被释放以后,要求写上p =NULL3、返回情况  返回的是一个void类型的指针,调用成功。(这就再你需要的时候进行强制类型转换)  返回NULL,当需要扩展的大小(第二个参数)为0并且第一个参数不为NULL,此时原内存变成了“freed(游离)”的了。  返回NULL,当没有足够的空间可供扩展的时候,此时,原内存空间的大小维持不变。
#include <stdio.h>
#include <math.h>
#include <malloc.h>
main()
{
char *p,*q;
p = (char * ) malloc (10);
if(p)
printf("Memory p Allocated at: %x",p);
else
printf("Not Enough Memory!\n");
getchar();
q=p;
p = (char * ) realloc (p,pow(2,32));//注意不能写成2^32
if(p)
printf("Memory p Reallocated at: %x\n",p);
else
printf("Not Enough Memory!\n");
printf("Memory Reallocated at: %x\n",p);
getchar();
free(p);
printf("Memory p Reallocated at: %x\n",p);
printf("Memory q Reallocated at: %x\n",q);
return 0;
}  4、特殊情况  如果mem_address为null,则realloc()和malloc()类似。分配一个newsize的内存块,返回一个指向该内存块的指针。  如果newsize大小为0,那么释放mem_address指向的内存,并返回null。  如果没有足够可用的内存用来完成重新分配(扩大原来的内存块或者分配新的内存块),则返回null.而原来的内存块保持不变。
malloc  原型:extern void *malloc(unsigned int num_bytes);
  用法:#include <malloc.h>
  或#include<stdlib.h>
  功能:分配长度为num_bytes字节的内存块
  说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
  当内存不再使用时,应使用free()函数将内存块释放。
  malloc的语法是:指针名=(数据类型*)malloc(长度),(数据类型*)表示指针.
  举例:
  // malloc.c
#include <stdio.h>
#include <malloc.h>
main()
{
char *p;
p=(char *)malloc(100);
if(p)
printf("Memory Allocated at: %x",p);
else
printf("Not Enough Memory!\n");
if(p)
free(p);
getchar();
return 0;
} malloc()函数的工作机制  malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。浅析malloc()的几种实现方式  malloc()是C语言中动态存储管理的一组标准库函数之一。其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。
  动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。本文简单介绍动态内存分配函数malloc()及几种实现方法。
  1. 简介
  malloc()是C语言中动态存储管理的一组标准库函数之一。其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。
  2. 函数说明
  C语言的动态存储管理由一组标准库函数实现,其原型在标准文件<stdlib.h>里描述,需要用这些功能时应包含这个文件。与动态存储分配有关的函数共有四个,其中就包括存储分配函数malloc()。函数原型是:void *malloc (size_t n);这里的size_t是标准库里定义的一个类型,它是一个无符号整型。这个整型能够满足所有对存储块大小描述的需要,具体相当于哪个整型由具体的C系统确定。malloc的返回值为(void *)类型(这是通用指针的一个重要用途),它分配一片能存放大小为n的数据的存储块,返回对应的指针值;如果不能满足申请(找不到能满足要求的存储块)就返回NULL。在使用时,应该把malloc的返回值转换到特定指针类型,赋给一个指针。
  注意,虽然这里的存储块是通过动态分配得到的,但是它的大小也是确定的,同样不允许越界使用。例如上面程序段分配的块里能存n个双精度数据,随后的使用就必须在这个范围内进行。越界使用动态分配的存储块,尤其是越界赋值,可能引起非常严重的后果,通常会破坏程序的运行系统,可能造成本程序或者整个计算机系统垮台。
  下例是一个动态分配的例子:
#include <stdio.h>
#include<stdlib.h>
main()
{
int count,*array;
count=10;
if((array=(int *)malloc(count*sizeof(int))) == NULL)
{
printf("不能成功分配存储空间。");
exit(1);
}
for(count=0;count<10;count++)
array[count]=count;
for(count=0;count<10;count++)
printf("%2d",array[count]);
}
  上例中动态分配了10个整型存储区域,然后进行赋值并打印。例中if((array(int *) malloc (10*sizeof(int)))==NULL)语句可以分为以下几步:
  1)分配10个整型的连续存储空间,并返回一个指向其起始地址的整型指针
  2)把此整型指针地址赋给array
  3)检测返回值是否为NULL
  3. malloc()工作机制
  malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。
  4. malloc()在操作系统中的实现
  在 C 程序中,多次使用malloc () 和 free()。不过,您可能没有用一些时间去思考它们在您的操作系统中是如何实现的。本节将向您展示 malloc 和 free 的一个最简化实现的代码,来帮助说明管理内存时都涉及到了哪些事情。
  在大部分操作系统中,内存分配由以下两个简单的函数来处理:
  void *malloc (long numbytes):该函数负责分配 numbytes 大小的内存,并返回指向第一个字节的指针。
  void free(void *firstbyte):如果给定一个由先前的 malloc 返回的指针,那么该函数会将分配的空间归还给进程的“空闲空间”。
  malloc_init 将是初始化内存分配程序的函数。它要完成以下三件事:将分配程序标识为已经初始化,找到系统中最后一个有效内存地址,然后建立起指向我们管理的内存的指针。这三个变量都是全局变量:
  清单 1. 我们的简单分配程序的全局变量
  int has_initialized = 0;
  void *managed_memory_start;
  void *last_valid_address;
  如前所述,被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者 当前中断点。在很多 UNIX? 系统中,为了指出当前系统中断点,必须使用 sbrk(0) 函数。 sbrk 根据参数中给出的字节数移动当前系统中断点,然后返回新的系统中断点。使用参数 0 只是返回当前中断点。这里是我们的 malloc 初始化代码,它将找到当前中断点并初始化我们的变量:
  清单 2. 分配程序初始化函数
  
  #include
  void malloc_init()
  {
  
  last_valid_address = sbrk(0);
  
  managed_memory_start = last_valid_address;
  
  has_initialized = 1;
  }
  现在,为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。在对内存块进行了 free 调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用 malloc 时,我们要能够定位未被使用的内存块。因此, malloc 返回的每块内存的起始处首先要有这个结构:
  清单 3. 内存控制块结构定义
  struct mem_control_block {
  int is_available;
  int size;
  };
  现在,您可能会认为当程序调用 malloc 时这会引发问题 —— 它们如何知道这个结构?答案是它们不必知道;在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。这使得返回的指针指向没有用于任何其他用途的内存。那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。然后,当通过 free() 将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。
  在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退 sizeof(struct mem_control_block) 个字节,并将其标记为可用的。这里是对应的代码:
  清单 4. 解除分配函数
  void free(void *firstbyte) {
  struct mem_control_block *mcb;
  
  mcb = firstbyte - sizeof(struct mem_control_block);
  
  mcb->is_available = 1;
  
  return;
  }
  如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。分配内存稍微困难一些。以下是该算法的略述:
  清单 5. 主分配程序的伪代码
  1. If our allocator has not been initialized, initialize it.
  2. Add sizeof(struct mem_control_block) to the size requested.
  3. start at managed_memory_start.
  4. Are we at last_valid address?
  5. If we are:
  A. We didn't find any existing space that was large enough
  -- ask the operating system for more and return that.
  6. Otherwise:
  A. Is the current space available (check is_available from
  the mem_control_block)?
  B. If it is:
  i) Is it large enough (check "size" from the
  mem_control_block)?
  ii) If so:
  a. Mark it as unavailable
  b. Move past mem_control_block and return the
  pointer
  iii) Otherwise:
  a. Move forward "size" bytes
  b. Go back go step 4
  C. Otherwise:
  i) Move forward "size" bytes
  ii) Go back to step 4
  我们主要使用连接的指针遍历内存来寻找开放的内存块。这里是代码:
  清单 6. 主分配程序
  void *malloc(long numbytes) {
  
  void *current_location;
  
  struct mem_control_block *current_location_mcb;
  
  void *memory_location;
  
  if(! has_initialized) {
  malloc_init();
  }
  
  numbytes = numbytes + sizeof(struct mem_control_block);
  
  memory_location = 0;
  
  current_location = managed_memory_start;
  
  while(current_location != last_valid_address)
  {
  
  current_location_mcb =
  (struct mem_control_block *)current_location;
  if(current_location_mcb->is_available)
  {
  if(current_location_mcb->size >= numbytes)
  {
  
  
  current_location_mcb->is_available = 0;
  
  memory_location = current_location;
  
  break;
  }
  }
  
  current_location = current_location +
  current_location_mcb->size;
  }
  
  if(! memory_location)
  {
  
  sbrk(numbytes);
  
  memory_location = last_valid_address;
  
  last_valid_address = last_valid_address + numbytes;
  
  current_location_mcb = memory_location;
  current_location_mcb->is_available = 0;
  current_location_mcb->size = numbytes;
  }
  
  
  memory_location = memory_location + sizeof(struct mem_control_block);
  
  return memory_location;
  }
  这就是我们的内存管理器。现在,我们只需要构建它,并在程序中使用它即可。
  5. malloc()的其他实现
  malloc() 的实现有很多,这些实现各有优点与缺点。在设计一个分配程序时,要面临许多需要折衷的选择,其中包括:
  分配的速度。
  回收的速度。
  有线程的环境的行为。
  内存将要被用光时的行为。
  局部缓存。
  簿记(Bookkeeping)内存开销。
  虚拟内存环境中的行为。
  小的或者大的对象。
  实时保证。
  每一个实现都有其自身的优缺点集合。在我们的简单的分配程序中,分配非常慢,而回收非常快。另外,由于它在使用虚拟内存系统方面较差,所以它最适于处理大的对象。
  还有其他许多分配程序可以使用。其中包括:
  Doug Lea Malloc:Doug Lea Malloc 实际上是完整的一组分配程序,其中包括 Doug Lea 的原始分配程序,GNU libc 分配程序和 ptmalloc。 Doug Lea 的分配程序有着与我们的版本非常类似的基本结构,但是它加入了索引,这使得搜索速度更快,并且可以将多个没有被使用的块组合为一个大的块。它还支持缓存,以便更快地再次使用最近释放的内存。 ptmalloc 是 Doug Lea Malloc 的一个扩展版本,支持多线程。在本文后面的 参考资料部分中,有一篇描述 Doug Lea 的 Malloc 实现的文章。
  BSD Malloc:BSD Malloc 是随 4.2 BSD 发行的实现,包含在 FreeBSD 之中,这个分配程序可以从预先确实大小的对象构成的池中分配对象。它有一些用于对象大小的 size 类,这些对象的大小为 2 的若干次幂减去某一常数。所以,如果您请求给定大小的一个对象,它就简单地分配一个与之匹配的 size 类。这样就提供了一个快速的实现,但是可能会浪费内存。在 参考资料部分中,有一篇描述该实现的文章。
  Hoard:编写 Hoard 的目标是使内存分配在多线程环境中进行得非常快。因此,它的构造以锁的使用为中心,从而使所有进程不必等待分配内存。它可以显著地加快那些进行很多分配和回收的多线程进程的速度。在参考资料部分中,有一篇描述该实现的文章。
  众多可用的分配程序中最有名的就是上述这些分配程序。如果您的程序有特别的分配需求,那么您可能更愿意编写一个定制的能匹配您的程序内存分配方式的分配程序。不过,如果不熟悉分配程序的设计,那么定制分配程序通常会带来比它们解决的问题更多的问题。
  6. 结束语
  前面已经提过,多次调用malloc()后空闲内存被切成很多的小内存片段,这就使得用户在申请内存使用时,由于找不到足够大的内存空间,malloc()需要进行内存整理,使得函数的性能越来越低。聪明的程序员通过总是分配大小为2的幂的内存块,而最大限度地降低潜在的malloc性能丧失。也就是说,所分配的内存块大小为4字节、8字节、16字节、18446744073709551616字节,等等。这样做最大限度地减少了进入空闲链的怪异片段(各种尺寸的小片段都有)的数量。尽管看起来这好像浪费了空间,但也容易看出浪费的空间永远不会超过50%。
原创粉丝点击