您好,欢迎来到微智科技网。
搜索
您的当前位置:首页内存分配

内存分配

来源:微智科技网
浅析malloc()的几种实现方式

malloc()是c语言中动态存储管理的一组标准库函数之一。其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。

动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。本文简单介绍动态内存分配函数malloc()及几种实现方法。

1. 简介

malloc()是c语言中动态存储管理的一组标准库函数之一。其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个null指针。所以在调用该函数时应该检测返回值是否为null并执行相应的操作。

2. 函数说明

c语言的动态存储管理由一组标准库函数实现,其原型在标准文件里描述,需要用这些功

能时应包含这个文件。与动态存储分配有关的函数共有四个,其中就包括存储分配函数malloc()。函数原型是:void *malloc (size_t n);这里的size_t是标准库里定义的一个类型,它是一个无符号整型。这个整型能够满足所有对存储块大小描述的需要,具体相当于哪个整型由具体的c系统确定。malloc的返回值为(void *)类型(这是通用指针的一个重要用途),它分配一片能存放大小为n的数据的存储块,返回对应的指针值;如果不能满足申请(找不到能满足要求的存储块)就返回null。在使用时,应该把malloc的返回值转换到特定指针类型,赋给一个指针。

注意,虽然这里的存储块是通过动态分配得到的,但是它的大小也是确定的,同样不允许越界使用。例如上面程序段分配的块里能存n个双精度数据,随后的使用就必须在这个范围内进行。越界使用动态分配的存储块,尤其是越界赋值,可能引起非常严重的后果,通常会破坏程序的运行系统,可能造成本程序或者整个计算机系统垮台。 下例是一个动态分配的例子: #include

main() {

int count,*array; /*count是一个计数器,array是一个整型指针,也可以理解为指向一个整型数组的首地址*/ if((array(int *) malloc (10*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 the sbrk function */

#include

void malloc_init() {

/* grab the last valid address from the os */ last_valid_address = sbrk(0);

/* we dont have any memory to manage yet, so *just set the beginning to be last_valid_address */

managed_memory_start = last_valid_address;

/* okay, were initialized and ready to go */ 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;

/* backup from the given pointer to find the * mem_control_block */

mcb = firstbyte - sizeof(struct mem_control_block); /* mark the block as being available */ mcb->is_available = 1;

/* thats it! were done. */ 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 didnt 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) {

/* holds where we are looking in memory */ void *current_location;

/* this is the same as current_location, but cast to a * memory_control_block */

struct mem_control_block *current_location_mcb;

/* this is the memory location we will return. it will * be set to 0 until we find something suitable */

void *memory_location;

/* initialize if we havent already done so */ if(! has_initialized) { malloc_init(); }

/* the memory we search for has to include the memory * control block, but the users of malloc dont need * to know this, so well just add it in for them. */

numbytes = numbytes + sizeof(struct mem_control_block); /* set memory_location to 0 until we find a suitable * location */

memory_location = 0;

/* begin searching at the start of managed memory */ current_location = managed_memory_start;

/* keep going until we have searched all allocated space */ while(current_location != last_valid_address) {

/* current_location and current_location_mcb point * to the same address. however, current_location_mcb * is of the correct type, so we can use it as a struct. * current_location is a void pointer so we can use it * to calculate addresses. */

current_location_mcb =

(struct mem_control_block *)current_location; if(current_location_mcb->is_available) {

if(current_location_mcb->size >= numbytes) {

/* woohoo! weve found an open, * appropriately-size location. */

/* it is no longer available */ current_location_mcb->is_available = 0; /* we own it */

memory_location = current_location; /* leave the loop */ break; } }

/* if we made it here, its because the current * block not suitable; move to the next one */

current_location = current_location + current_location_mcb->size; }

memory /* if

we still dont have a valid location, well

* have to ask the operating system for more memory */

if(! memory_location) {

/* move the program break numbytes further */ sbrk(numbytes);

/* the new memory will be where the last valid * address left off */

memory_location = last_valid_address;

/* well move the last valid address forward * numbytes */

last_valid_address = last_valid_address + numbytes; /* we need to initialize the mem_control_block */ current_location_mcb = memory_location; current_location_mcb->is_available = 0; current_location_mcb->size = numbytes; }

/* now, no matter what (well, except for error conditions), * memory_location has the address of the memory, including * the mem_control_block */

/* move the pointer past the mem_control_block */

memory_location = memory_location + sizeof(struct mem_control_block); /* return the pointer */ 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%。

嵌入式下malloc()+free()的实现代码 /*************************************Seekfor FAT System v1.3****

Seekfor FAT system v1.3是移植性非常强的一个嵌入式FAT16/FAT32文件系统软件包,支持多个物理驱动器,完全兼容DOS下的文件系统,支持多任务下的文件操作. <1>和SFS 前期版本比较,SFS v1.3增加以下特性: 1.增强了错误处理功能 2.增加了FAT_format()功能 3.开始支持扩展分区

4.开始支持长文件的显示(暂不支持用长文件名访问文件),Windows下建立的长文件名可以正常显示

5.取消v1.0中的物理驱动器概念,所有驱动器都看做逻辑驱动器 6.修正v1.0中扇区计算不对不兼容Windows的bug 7.修正FAT32根目录起始位置固定的bug

8.内部直接支持malloc()+free()机制,用户只需提供2K以上的RAM空间即可使用SFS v1.3

<2>更新软件请联系: QQ:82054357

MSN:sfrad32@hotmail.com Mail:Seek_for@163.com <3>本文件说明 a.文件名:malloc.c b.功能:动态内存分配

**************************************************************************************************************************************/

#include \"malloc.h\"

static DWORD dwUnits=0;/*每个分配单元字节数*/ static DWORD dwNumOfUnits=0;/*分配单元总数*/ static BYTE*lpszMAT=(BYTE*)0;/*MAT表首地址*/ static BYTE*lpszMEM=(BYTE*)0;/*MEM首地址*/

static BYTE MASK[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; static BYTE bit_is_set(DWORD offset) {

BYTE*item; BYTE i;

item=lpszMAT+(offset>>3); i=offset&0x07;

return ((*item)&MASK[i]); }

static void set_bit(DWORD offset) {

BYTE*item; BYTE i;

item=lpszMAT+(offset>>3); i=offset&0x07; *item|=MASK[i]; }

static void clear_bit(DWORD offset) {

BYTE*item; BYTE i;

item=lpszMAT+(offset>>3); i=offset&0x07; *item&=~MASK[i]; }

/**void MEM_init(BYTE*ptr,DWORD len,DWORD

alloc_unit)******************************************** 功能:

内存模块重新初始化 入口:

BYTE*ptr:内存首地址 DWORD len:内存块大小

DWORD alloc_unit:每单元字节数,在ARM下一定要是4的倍数,否则在使用DWORD*会产生DATA abort中断 出口: 无 调用者: 任意 备注: 无

*************************************************************************************************/

void MEM_init(BYTE*ptr,DWORD len,DWORD alloc_unit) {

DWORD dwBytes;

dwUnits=(alloc_unit==0)?DEFAULT_ALLOC_UNIT:alloc_unit;/*检测分配单元是否有效,如果无效则使用默认设置*/

dwNumOfUnits=(len*8-7)/(dwUnits*8+1);/*检测总的分配单元数*/ dwBytes=(dwNumOfUnits+7)/8;

if(dwBytes&0x03)/*不是字对齐则分配单元减去1*/ {

dwNumOfUnits--; }

dwBytes=dwBytes+(4-(dwBytes&0x03));/*DWORD对齐*/ for(len=0;lenlpszMAT=ptr;

lpszMEM=ptr+dwBytes; } /**void

free(void*)************************************************************************** 功能:

free一个内存块(在MAT表中所有使用项目全部置0) 入口:

void*ptr:malloc()返回的内存块指针 出口: 无 调用者: 任意 备注: 无

*************************************************************************************************/ void free(void*ptr) {

DWORD offset,i,j; DWORD*mem;

if((DWORD)ptr<(DWORD)(lpszMEM+sizeof(DWORD)*2)) return; mem=((DWORD*)ptr); mem-=2; offset=*mem++; i=*mem++; for(j=0;jclear_bit(offset+j); } }

/**void malloc(DWORD

size)************************************************************************** 功能:

动态分配一个size大小的内存块 入口:

DWORD size:要分配的大小,字节数 出口:

调用成功返回一个有效指针,否则返回NULL 调用者: 任意 备注: 无

*************************************************************************************************/ void*malloc(DWORD size) {

DWORD i,j; DWORD blocks; DWORD total; DWORD*ptr;

if(!lpszMAT||!lpszMEM) return (void*)0;

total=(size+(dwUnits-1)+sizeof(DWORD)*2)/dwUnits;/*计算需要的单元总数*/ for(i=0;iif(bit_is_set(i)) continue; blocks=0;

for(j=i;jif(bit_is_set(j)) break; if(++blocks==total) break; }

if(blocks>=total) {

ptr=(DWORD*)(lpszMEM+i*dwUnits); *ptr++=i; *ptr++=blocks;

for(j=0;jreturn (void*)0; }

以上库的使用方法

unsigned char MEM[1024*32];/*32K的动态空间*/ void main() { char*p;

MEM_init((char*)MEM,1024*32,32); p=(char*)malloc(256*sizeof(char)); if(p) {

strcpy(p,\"This is a demo program!\\r\\n\");

printf(p); free(p); } }

C程序中的内存管理 相比静态地分配内存空间,使用动态内存分配具有明显的优势: 1, 分配空间的大小够精确: 设想一个读取用户输入行的程序, 如果使用静态分配的数组作为buffer, 那么, 你如何确定该数组的长度呢? 太大或太小都不合适. 因为你无法事先知道用户输入字符串的长度. 而使用动态内存分配就精准多了. 2, 静态分配的空间大小无法更改, 而动态分配的内存大小是可调的. 所以, 理解C语言中的动态内存分配对于编写实用, 有效, 安全的程序来说必不可少. 本文假设你使用C语言编程, 且使用GNU/Linux系统. (其实由于现在的许多系统都是POSIX兼容的, 本文的内容使用于任何操作系统, 只是其中提到的某些工具仅存于GNU/Linux上.) 要理解内存管理, 首先要理解程序在内存中的布局, 既: 内存程序映像. 可参考本blog的GNU/Linux平台的C程序开发及程序运行环境 标准C中的内存管理函数 函数原型如下: #include void *malloc(size_t size); void *calloc(size_t nobj, size_t size); void *realloc(void *ptr, size_t newsize); 若返回的指针=NULL, 则失败, 否则成功. void free(void *ptr); ISO C 上表列出了标准C规定的四个常用内存管理函数, 一般而言, 这4个函数已经足够我们进行内存管理了. malloc(), calloc(), realloc()返回的指针若不为NULL, 那么该指针指向被分配数据块中的第一个元素. 并且, 保证该指针能针对各种数据类型满足对齐要求. void *malloc(size_t size); 调用malloc()的步骤 1, 针对你想要存放数据的数据结构, 声明一个指向它的指针. 2, 计算你需要分配的字节数. 通常利用sizeof(数据结构) * n, n为你要使用的数据结构的个数. 3, 调用malloc()分配内存, 并将它所返回的通用指针(void *)显式地映射为指向该数据结构的指针, 并检查malloc()是否返回了NULL, 若返回值为NULL, 则进行错误处理. 实现代码 struct num { int x, y, z; }; struct num *p; int n; if ((p = (struct num *)malloc(n * sizeof(struct num))) == NULL) { 错误处理; } 使用p指向的内存; (记得初始化!) 标准C中规定, void *p是一个通用指针, 可以将它赋予任何类型的数据. 但在这里最好还是显式地将它的类型映射为需要分配的数据类型. why? 先看看下面替代上面使用malloc()函数的语句: if ((p = malloc(n * sizeof(*p))) != NULL) 这里将sizeof的参数换成了*P, 这样, 即便p被修改, 指向了不同的数据结构, sizeof也能计算正确的字节数. 这里省略了类型映射, 但加上它以后, 能够在p指向不同的数据结构后, 编译时给出警告信息. 总之, 我们使用这样的语句来调用malloc(): if ((p = (ds *)malloc(n * sizeof(*p))) == NULL) 其中(ds *)将通用指针显式地映射到要分配的数据类型. 另外, 传统c中使用char *作为通用指针, C++要求对malloc()的返回值进行显式的映射. void *realloc(void *ptr, size_t newsize); 使用realloc()可以调整之前分配的数据块大小. 包括增加或则减小. 一般而言不用减小已分配数据块的大小. 关于realloc()的原型, 有几点值得注意: 1, newsize是调整数据块大小后最终的值, 并非差量. 2, 若ptr != NULL && nesize == 0, 则 realloc(p, 0)等价于free(p). 3, ptr指向需要调整的数据块, 若ptr == NULL, relalloc(NULL, newsize)等价于malloc(newsize). 虽然可以用realloc()实现free()和malloc()的功能, 但不推荐这样做. 还是使用标准的接口比较合适. 调用realloc()的步骤: 1, 计算你新需要的字节数. 2, 找到指向你需要调整的数据块的指针(它是malloc()或calloc()甚至realloc()的返回值.), 并将它和新的字节大小传递给realloc(). 注意不要用增量! 3, 调用realloc()分配内存, 并将它所返回的通用指针(void *)显式地映射为指向该数据结构的指针, 并检查malloc()是否返回了NULL, 若返回值为NULL, 则进行错误处理. 注意: GNU Coding Standards规定: 即便realloc()失败, 之前分配的数据块也会保持不变, 可以继续使用. 实现代码 这里继续上述malloc()中的代码, 假设之前分配的n个num结构不够, 还需要再分配m个: struct num *q; int newsize = (n+m) * sizeof(*p); if ((q = (struct num *)realloc(p, newsize)) == NULL) { 错误处理; } p = q; 继续使用p; 注意: realloc()返回的地址赋给了一个新的指针q. 在调用完realloc()之后, 又将q的值赋给p , 继续使用p. 为何如此麻烦呢? 原因有二: (1)看看上面的框框, GNU保证即便realloc()返回NULL, 之前调用malloc()分配给p的数据块也能使用, 但若直接把realloc()的返回值赋给p, 可能令p = NULL, 使得之前p指向的数据段无法使用. (2)使用realloc()时, 脑子里应该时刻铭记一点: 由于对之前的数据块大小进行了调整, realloc()可能将以前的数据块挪到内存中别的位置. 考虑增大数据块的情况: 若之前分配的数据块所在的内存空间所剩的空间不够, 那么realloc()会将以前的数据块拷贝到内存中其他位置, 并释放之前分配的数据块. 这样之前的p就指向了无效的区域. 即便调用realloc()来减小数据块, 该数据块也可能被移到内存中的其他位置! 某个已分配的数据块b1, 调用realloc()调整b1大小得到b2之后, 不能假设b1和b2的第一个元素在同一位置. 指向b1的所有指针必须被更新! 引用原b1数据块中的元素有两种途径: 1, 使用数组下标. 2, 使用被更新后的指针. 绝不能使用以前指向p1的指针! void *calloc(size_t nobj, size_t size); calloc()可视为malloc()的一个封装, 下面是它可能的一个实现: void *calloc(size_t nobj, size_t size) { void *p; size_t total; total = nobj * size; if ((p = malloc(total)) != NULL) { memset(p, '\\0', total); return p; } 调用calloc()的方法与malloc()相同. calloc()与malloc()的区别在于两点: 1, calloc()将分配的内存数据块中的内容初始化为0. 这里的0指的是bitwise, 既每个位被清0, 具体的数值由要联系数据结构中各元素的类型. 2, 传递给calloc()的参数有2个, 第一个是想要分配的数据结构的个数, 第二个是数据结构的大小. 如果传递给malloc()或calloc()的size = 0, 标准C并未规定返回的指针一定为NULL, 它可能为非NULL. 但是这种情况下不能引用该指针. void free(void *ptr); 在完成对动态分配的数据块的使用之后, 要通过调用free()来释放它. 这里所说的\"释放\"是指将该数据块占用的内存放回到堆中, 以后再调用malloc(), calloc()或realloc()时可以利用该数据块占用的内存段. 注意free()并不能够改变进程地址空间的大小, 被释放的内存仍位于堆空间中. 如果不及时释放内存, 会引发内存泄露(memory leaks), 特别是运行时间比较长的程序要注意这个问题, 如果发生了内存泄露, 系统即便不因为缺少内存资源而崩溃, 也会由于内存抖动(memory thrashing)而性能下降. 调用free()的方法: free(p); p = NULL; 调用free()时, 有几点注意: 1, p必须指向由malloc(), calloc()或realloc()返回的地址. 即传递给free()的参数必须是数据块第一个元素的地址. 因为malloc()的实现往往在分配的数据块的首部存储一些用来管理分配的数据块的记账信息. 如果不将数据块首部地址传递给free(), free()无法知道数据块的具体信息, 也就无法释放. 把NULL传递给free()是合法的, free()不起任何作用. NULL == ((void *)0), 在现代系统上, 地址0不在进程地址空间之内, 引用0地址会引发段错误. 2, 谨防\"dangling pointer(野指针)\", 当p指向的数据块被释放后, p就成为了一个野指针. 如果再次通过p引用数据就存在问题了. p可能指向了内存中别的位置.( 如果在p被释放之后没有调用内存分配函数, p可能还指向原来的数据块, 但该情况不确定). 所以, 在调用free(p);之后, 要紧接着将p设为NULL. 这样如果引用p, 就会马上引起段错误. 不会干扰程序的其他地方. 3, 一个数据块只能被释放一次, 如果对同一数据块释放多次, 会引发问题. (多次调用free(NULL)不存在任何问题.) 4, 被释放的内存依然位于进程地址空间, 用于以后调用malloc(), calloc(), realloc()返回的数据块. 在栈上分配内存: alloca() 前面的malloc(), calloc(), realloc()都在堆上分配内存, 需要显式地释放所分配的内存. 如果使用alloca()在栈上分配内存, 由于每次函数返回时都会释放它所在的栈空间, alloca()所分配的内存会像动态变量一样被自动释放. alloca()的原型: #include void *alloca(size_t size); 不推荐使用alloca(), 因为它不属于ISO C或POSIX标准, 依赖于具体的系统和编译器, 即便在支持它的系统上, 它的实现也有bug. brk()和sbrk()系统调用 在UNIX系统中, malloc(), calloc(), realloc(), free()这4个函数都是在brk()和sbrk()这两个系统函数基础上实现的. 在应用程序中, 极少见到这两个函数, 这里对它们做一个简单介绍, 并利用它们来查看进程地址空间信息. brk(), sbrk()的原型: #include int brk(void *end_data_segment); void *sbrk(intptr_t increment); brk()j将进程地址空间的data段尾(既内存程序映像的堆尾)设置为end_data_segment所指向的位置. 若成功, 返回0, 否则返回-1. sbrk()使用差量来调整进程地址空间data段尾的位置, 并返回之前data段尾的地址. 下面看看这样一个程序, 它显示进程地址空间的相关信息: 1 /* 2 * Show address of code, data and stack sections, 3 * as well as BSS and dynamic memory. 4 */ 5 6 #include 7 #include /* for definition of ptrdiff_t on GLIBC */ 8 #include 9 #include /* for demonstration only */ 10 11 extern void afunc(void); /* a function for showing stack growth */ 12 13 int bss_var; /* auto init to 0, should be in BSS */ 14 int data_var = 42; /* init to nonzero, should be in data */ 15 16 int 17 main(int argc, char **argv) 18 { 19 char *p, *b, *nb; 20 int i; 21 printf(\"Text Locations:\\n\"); 22 printf(\"\Address of main: %p\\n\ 23 printf(\"\Address of afunc: %p\\n\ 24 25 printf(\"Stack Locations:\\n\"); 26 afunc(); 27 28 p = (char *) alloca(32); 29 if (p != NULL) { 30 printf(\"\Start of alloca()'ed array: %p\\n\ 31 printf(\"\End of alloca()'ed array: %p\\n\ 32 } 33 34 printf(\"Data Locations:\\n\"); 35 printf(\"\Address of data_var: %p\\n\ 36 37 printf(\"BSS Locations:\\n\"); 38 printf(\"\Address of bss_var: %p\\n\ 39 40 nb = sbrk((ptrdiff_t) 0); 41 printf(\"Heap Locations:\\n\"); 42 printf(\"\Initial end of heap: %p\\n\ 43 b = sbrk((ptrdiff_t) (32)); /* lower heap address */ 44 printf(\"\ sbrk return : %p\\n\ 45 46 47 nb = sbrk((ptrdiff_t) 0); 48 printf(\"\New end of heap: %p\\n\ 49 50 b = sbrk((ptrdiff_t) -16); /* shrink it */ 51 nb = sbrk((ptrdiff_t) 0); 52 printf(\"\Final end of heap: %p\\n\ 53 54 printf(\"Command-Line Arguments:\\n\"); 55 for (i = 0; argv[i] != NULL; i++) 56 printf(\"\Address of arg%d(%s) is %p\\n\ 57 58 return 0; 59 } 60 61 void 62 afunc(void) 63 { static int level = 0; /* recursion level */ 65 auto int stack_var; /* automatic variable, on stack */ 66 67 if (++level == 3) /* avoid infinite recursion */ 68 return; 69 70 printf(\"\Stack level %d: address of stack_var: %p\\n\ 71 level, & stack_var); 72 afunc(); /* recursive call */ 73 } 在Linux, x86系统中, 代码段开始于 0x08048000; 栈底地址开始于0xc0000000.

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 7swz.com 版权所有 赣ICP备2024042798号-8

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务