第十六章 动态内存分配
前言
- 先要搞明白一个问题,什么是动态分配,什么是静态分配?
- 举例来说,在声明数组时,必须明确告诉编译器数组的大小,之后编译器(程序在编译的时候就会分配内存)就会在内存中为该数组开辟固定大小的内存。有些时候,用户并不确定需要多大的内存,使用多大的数组,为了保险起见,有的用户采用定义一个大数组的方法,开辟的数组大小可能比实际所需大几倍甚至十几倍,这造成了内存的浪费,带来了极大的不便。另一方面,即使用户确切知道要存放的元素个数,但随着问题的深入,元素数目可能会变化,变少还好处理,可如果数目增加呢,用什么来存储的,类似于数组内存这种分配机制就称为静态分配,很明显,静态分配是由编译器完成的,在程序执行前便已指定。
- 显而易见,静态分配虽然直观,易理解,但存在明显的缺陷,不是容易浪费内存就是内存不够用,为解决这一问题,C语言引入了动态分配机制。
- 所谓动态分配,是指用户可以在程序运行期间根据需要申请或释放内存,大小也完全可控。动态分配不像数组内存那样需要预先分配空间,而是由系统根据程序需要动态分配,大小完全按照用户的要求来,当使用完毕后,用户还可释放所申请的动态内存,由系统回收,以备他用。
- 从前面我们知道:
- 内存是当定义一个变量时,系统才会分配内存给这个变量
- 这些内存要么是代码块结束时释放,要么是整个程序结束时释放,都是由系统自动控制
- 疑问:我们能不能,不定义变量而先分配一块内存,并且我们来控制他的销毁时刻呢?
- 堆区:就是这种情况,内存由程序员随时开辟,并且必须由程序员释放,否则只有等到程序结束会回收
- 由于我们直接分配了一段内存,而且不指明任何类型变量,那我们怎么来用这块内存呢?内存是由地址构成,因此只要我们拿到这块内存的第一个地址就行了,即用void *指针表示这个块内存
怎样建立内存的动态分配
对于内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,free,realloc这4个函数,函数的声明在stdlib.h文件中
- malloc函数
- 函数原型为:
void *malloc(unsigned int size);
-
其作用是在内存的动态存储区域中分配一个长度为size的连续存储空间.函数的返回值是所分配内存的第一个字节地址,或者说,此函数是一个指针函数,返回的指针指向该分配区域的开头位置.如:
//开辟100字节的零时分配域,函数值为第一个字节的地址 `malloc(100);`
- 注意:指针类型为void * ,即不指向任何数据类型,只提供一个地址,如果函数未能成功的执行(如内存空间不够),则返回空指针(NULL);
- 函数返回类型是void*,用其返回值对其他类型指针赋值时,必须进行显式转换。
- size仅仅是申请字节的大小,并不管申请的内存块中存储的数据类型,因此,申请内存的长度须由程序员通过“长度×sizeof(类型)”的方式给出,举例来说
int* p=(int*) malloc(5* sizeof(int) );
- 函数原型为:
- calloc函数
- C语言标准库函数还提供了calloc函数用以动态申请内存,和malloc函数以字节为单位申请内存不同,calloc函数是以目标对象为单位分配的,目标对象可以是数组,也可以是结构体等。
- 函数原型为:
void *calloc(unsigned n,unsigned size);
- calloc需要两个参数以指定申请内存块的大小,一是对象占据的内存字节数size,二是对象的个数num。
- 其作用是在内存的动态存储区域中分配n个长度为sized的连续空间,这个空间一般比较大,足以保存一个数组.
-
用calloc函数可以为一维数组开辟动态存储空间,n为数组元素的个数,每个元素的长度为size.这就是动态数组,函数返回指向所分配的起始位置的指针,如果分配不成功,则返回空指针(NULL);如:
//开辟5*40个 字节的临时分配区域,把起始地址赋值给指针变量p `p = calloc(50,4);`
- 函数返回类型也是void*,需要强制转换才能为其他类型的指针赋值。
- calloc会自动将内存初始化为0,malloc就不会
- free函数
- 函数原型为:
void free(void *p);
- 其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用.p应是最近一次调用calloc或者malloc函数时得到的函数返回值.如:
free(p);
- 函数free无返回值.
- 函数原型为:
- realloc函数
- 函数原型为:
void *realloc(void *p ,unsigned int size);
- 如果已经通过malloc或者calloc函数申请了一些动态存储空间,想改变其大小,可以用realloc函数重新分配
- 函数有两个参数
- 已分配的内存地址
- 重新分配的字节数
- 用realloc函数将p所指向的动态空间的大小改变为size.p的值不变,如果重分配不成功,返回NULL.如:
realloc(p,50);
- relloc 如果可以拓展就拓展,否则就重新分配
- 拓展就是在原来地址后面增加内存。
- 不够的情况下,就回收原来的内存,并在回收之前分配一片内存,将原来的内容拷贝过来。然后收回原来的内存
- 函数原型为:
动态申请内存分析
- 下例,p是main函数中定义的变量,执行free(p)操作对p有什么影响呢?p会被删除么?如果没有被删除,p的值是多少,自动变为NULL么?看完,这些问题便迎刃而解。
-
代码‑ free函数把指针怎么了
#include <stdio.h> /*使用printf要包含的头文件*/ #include <stdlib.h> void main(void) /*主函数*/ {/*声明一int型指针p,并申请一块动态内存,用其首地址为p初始化*/ int *p=(int*)malloc(10*sizeof(int)); if(p==NULL) /*防错处理,看内存申请是否成功*/ { printf("内存申请失败,退出"); return; } printf("p的值是%p\n",p); /*输出p的值*/ free(p); /*释放动态内存*/ printf("p的值是%p\n",p); /*输出p的值*/ //安全释放,让指针p指向NULL p = NULL; getchar(); /*等待,按任意键继续*/ } // 输出结果为: p的值是0x00146430 p的值是0x00146430
- 可以看出free函数仅仅是找到指针p所指的首地址的内存释放掉
- 对指针p没有作用,指针p仍然指向原来的地址
- 但是注意此时的p指向的是一块已经被释放掉的内存
- 指针p不能在使用
- 此时指针p指向的是已经释放的内存,而且内存中存在的是一些垃圾数据,如果使用会造成不可预知的问题
- 此时的指针也叫迷途指针
- 指针p不能再次被释放
- 重复释放已经释放的内存,会崩溃
- 指针的安全释放
- 如下;
free(p); p = NULL;
- 如果此时将p = NULL,就可以再次被释放,即只有NULL指针可以被反复释放
- 而且如果使用NULL指针,程序运行的时候就会提示,不会造成问题。
- 如下;
- 指针p不能在使用
释放已经释放了的内存会出问题
- 既然使用已经释放了的内存是非法的,那释放已经释放了的内存会怎样的?一般来说,程序同样会崩溃。用户一般不会对同一指针多次释放,但如所示的错误却经常会犯!!!!。
-
代码‑ 释放已经释放了的内存
#include <stdio.h> /*使用printf要包含的头文件*/ #include <stdlib.h> void main(void) /*主函数*/ {/*声明一int型指针p,并申请一块动态内存,用其首地址为p初始化*/ int *p=(int*)malloc(10*sizeof(int)); if(p==NULL) /*防错处理,看内存申请是否成功*/ { printf("内存申请失败,退出"); return; } //经常犯的错误!!!!!!不同指针指向同一块内存,然后对每个指针进行释放。 int *z=p; free(p); /*释放动态内存*/ free(z);/*再次释放内存*/ getchar();/*等待,按任意键继续*/ }
内存泄漏
- 已经知道“释放动态内存,并不意味着指针会消亡,也不意味着指针的值会改变”
- 但“指针消亡,动态内存是否会自动释放呢”?
-
否,如果没有释放内存,但记录该块内存的指针消亡了或者是指针的值发生了改变,这块内存将永远得不到回收,造成了内存泄漏,如果程序长时间运行的话,不断的泄漏可能使得系统内存耗尽而崩溃。
#include <stdio.h> #include <stdlib.h> void main(void) { //分配的内存永远不会被释放 int *p = (int*)malloc(10 * sizeof(int)); p = NULL; getchar(); }