malloc函数和free函数
假设您的程序在执行过程中需要分配一定量的内存。您可以随时调用malloc函数从堆中申请一块内存。在操作系统为您的程序预留出这块内存,之后您就可以随意使用它了。用完之后,要使用free函数将这块内存返回给操作系统进行回收。以后其他程序还可以按自己的需要预留这块内存。
作为例子,下面的代码演示了最简单的使用堆的方法:
int main() {int *p;p = (int *)malloc(sizeof(int));if (p==0) {printf("错误:内存不足n");return 1; }*p=5;printf("&dn", *p);free(p);return 0;}
程序的开始调用了malloc函数,这个函数做了三件事:
- malloc语句首先检查堆上的空闲内存总数,然后判断:“有没有足够的空闲内存可以分配一个所申请的大小的内存块呢?”申请的内存块大小是由传入malloc的参数确定的——本例中的sizeof(int)是4个字节。若内存不足,malloc函数会返回零地址告知发生的错误(零地址的另一种表示是NULL,它在C代码中很常用)。否则malloc函数继续执行。
- 若堆上有足够的内存,系统就从堆上“分配”或“预留”出指定大小的内存块。预留的目的是为了防止多个malloc语句恰巧使用同一个内存块。
- 接下来系统将预留出的内存块的地址保存到指针变量中(本例中就是p)。指针变量本身保存了一个地址。被分配的内存块能够存储一个指定类型的数值,而指针正是指向此数值。
下图显示了调用malloc之后的内存状态:
右边的方框表示malloc分配的内存块。
接着程序用if (p==0)检查指针p以确定分配申请成功(此行也可写成if (p==NULL)甚至if (!p))。如果分配失败(p等于零),则程序终止,否则程序将分配的内存块初始化为5,然后打印内存块的值,接着调用free函数将内存块返还给堆,最后退出。
前面的章节有一段代码是将p赋值为一个现成整数i的地址,而本例中的代码和那段代码实际上并无不同。区别只是在于:对于变量i的内存,它是程序预分配内存空间的一部分,有两个名字i和*p;而对于从堆上分配的内存,它只有一个名字*p,且是在程序运行中分配的。两个常见的问题是:
- 每次分配内存后都要检查指针的值是否为零,这真的很重要吗?是的。因为堆的大小取决于当前正在运行哪些程序、它们分配了多少内存等诸多因素,所以一直都在变化,不能保证调用malloc总是成功的。每次调用malloc以后,您都应该检查一下指针以确保其有效性。
- 如果程序结束前我忘记了释放分配的内存块会怎样呢?程序结束以后,操作系统会做“善后处理”:释放可执行代码、栈、全局变量和所有从堆上分配的内存空间以供回收利用。因此,对分配的内存置之不理,在程序结束以后是不会对系统造成持续影响的。但是这种做法会被认为是“不良的风格”,且程序运行中的“内存泄漏”是有害的。这一点下文还会讲到。
下面两段程序显示了两种不同的使用指针的正确方法,旨在区分指针和指针的值在使用上的区别:
void main() {int*p, *q;p=(int *)malloc(sizeof(int));q=p;*p=10;printf("%dn", *q);*q=20; printf("%dn", *q);}
此程序的最后输出结果是代码第4行打印的10和代码第6行打印的20。下面是一个内存状态示意图:
下面这个程序稍有不同:
void main() {int *p, *q;p=(int *)malloc(sizeof(int));q=(int *)malloc(sizeof(int)); *p=10;*q=20;*p=*q;printf("%dn", *p);}
此程序的最后输出结果是代码第6行打印的20。下面是它的内存状态示意图:
注意,编译器会接受*p=*q,因为*p和*q都是整数。这条语句的意思是说:“将q指向的整数传送到p指向的整数中去。”被传送的是数值。编译器也会接受p=q,因为p和q都是指针且指向相同的类型(若s为指向字符的指针则p=s是不允许的,因为它们指向不同类型)。p=q这条语句的意思是说:“将p指向和q相同的内存位置。”换句话说,q指向的地址被传送到了p,因此两个指针指向相同的地址。被传送的是地址。
从这些例子可以知道,初始化指针的方式有四种。在程序中声明一个指针时(如int *p),它开始处于未初始化状态。它可能指向任何位置,因此对它的解引用(取出指针指向的地址中的内容)是错误的。初始化指针就是将其指向一个已知的内存地址。
-
第一种方式是例子中使用的malloc语句。此语句从堆上分配一块内存并将指针指向它。这样指针便完成了初始化,因为它现在保存了一个有效地址,即新分配的内存块的地址。
-
第二种方式,也是刚才用到的,是用p=q这样的语句使p指向和q相同的位置。若q已经指向有效地址,则p完成初始化。指针p将保存q已经保存的有效地址。但若q未初始化或无效,则这个无价值的地址也会传给 p。
-
第三种方式是将指针指向已知地址,如一个全局变量的地址。例如,若i是一个整数且p是一个整型指针,则语句p=&i初始化p为指向i。
-
第四种方式是将指针初始化为零。在使用指针时零是一个特殊值,如下所示:
p=0;
或:
p=NULL;
此语句完成的操作是将零赋给p。指针p指向的地址为零。一般用下面的示意图表示这种情况:
任何指针都可设为指向零地址。虽然p指向零地址,但是它却不指向任何真正的内存块。此指针保存的零值只是一个标志。可以像下面语句这样使用它:
if (p==0) {...}
或:
while (p!=0){...}
系统会识别零值,如果您无意中解引用一个零指针,系统会报错。例如下列代码:
p=0;*p=5;
程序一般会崩溃。指针p不指向内存块而是零地址,所以不能为*p赋值。后面我们讲到链表时,零指针将被作为一个标志使用。
malloc命令用于分配一个内存块。当此内存块不再需要时还可以将其释放。释放的内存块可以被后来的malloc语句重新分配,这样系统就可以回收内存。释放内存的命令叫做free,它接受一个指针作为参数。free命令完成两件事情:
- 不再预留指针指向的内存块,而是将其返还到堆上的空闲内存区。此内存块可以被随后的语句重新使用。
- 指针被置为未初始化的状态,再次使用前必须重新初始化。
free语句只是将指针还原为未初始化状态并使内存块在堆上重新变成可用状态。
下例显示了如何使用堆。它分配了一块整数内存,写入数据然后输出,最后废除此内存块:
#includeint main() {int *p;p=(int *)malloc (sizeof(int));*p=10;printf("%d n",*p);free(p);return 0;}
此代码其实只适用于在C中演示分配、使用和释放内存块的过程。malloc用于分配一块指定大小的内存,本例中是sizeof(int)字节(4字节)。C语言的sizeof命令以字节为单位返回任何类型的大小。代码中完全可以写成malloc(4),因为在大部分机器上sizeof(int)等于4个字节。但是使用sizeof可以大大增强代码的可移植性和可读性。
malloc函数返回一个指向被分配内存块的指针。这是一个通用指针,若不经类型转换即使用一般会导致编译器发出类型警告。类型转换(int *)将malloc返回的通用指针转换为一个“指向整数的指针”,即与p一致。C中的free语句将内存块返还给堆以供重新使用。
第二个例子说明的函数和前一例相同,但是用结构体代替了整数。C代码如下:
#includestruct rec {int i;float f;char c;}; int main() {struct rec *p;p=(struct rec *) malloc (sizeof(struct rec));(*p).i=10; (*p).f=3.14; (*p).c='a'; printf("%d %f %c n",(*p).i,(*p).f,(*p).c); free(p); return 0;}
请注意这行:
(*p).i=10;
很多人不明白为什么不能写成:
*p.i=10;
答案是这和C语言的操作符优先级有关。5+3*4的结果是17,不是32,因为在大多数计算机语言中*比+有更高的优先级。C语言中,操作符.比*有更高的优先级,所以要使用括号保持正确的操作顺序。
但是大部分人觉得总是输入(*p).i太麻烦了,因此C提供了一种简洁记法。下面的两条语句完全等效,而第二条的输入更加简便:
(*p).i=10; p->i=10;
阅读别人代码的时候,您会发现第二种记法比第一种更常用。
评论
查看更多