一、 三个存储区存储的数据种类
| 栈区 | 存放 局部变量、函数的形式参数 |
| 堆区 | 动态内存的分配 |
| 静态区 | 存放 全局变量 、静态变量 |
二、动态内存的分配
目录
| 1.为什么要用动态内存的开辟 |
| 2.开辟和释放动态内存的函数 |
| 3.这些函数的注意事项 |
我目前掌握的开辟内存的方式是
1.使用变量在栈区开辟空间
int a=5; float b=3.4;
2.使用数组在栈区开辟一段连续的空间,然后进行操作。
int arr[10]={0};
但是,针对第二种开辟方式,我们会遇到两个问题,
1)申请的内存不够用或有剩余
2)内存的长度必须是确定的
在我使用的DEV-C++和VS2013中,是不允许存在变长数组的,即以下代码是不正确的。
int n;
scanf("%d",&n);
intt arr[n];
所以,为了更合理的使用内存,我们要学习动态内存的开辟。
2.开辟和释放动态内存的函数2.1malloc和free
void* malloc (size_t size);
这是malloc的函数原型,观察可知,它的形参是我们要开辟空间的大小,返回值是一个地址,它是该内存起始位置的地址。
如果开辟成功,则返回一个指向开辟好空间的指针。 如果开辟失败,则返回一个 NULL 指针,因此 malloc 的返回值一定要做检查。void free (void* ptr);这是free 的函数原型,它的形参是指针变量,作用是释放开辟的动态内存。 2.1.1malloc和free的用法
malloc和free都声明在 stdlib.h 头文件中。
#include#include int main() { int* ptr = NULL;//定义一个指针变量,用来接收malloc的返回值 ptr = (int*)malloc(num*sizeof(int)); //num是一个整数,sizeof(int)是整形变量占的字节数(4或2,取决于编译器) //即开辟num个大小为4个字节的内存; //ptr = (int*)malloc(num);这样写也是可以的 if(NULL != ptr)//判断ptr指针是否为空 { int i = 0; for(i=0; i
2.2 callocvoid* calloc (size_t num, size_t size);这是calloc的函数原型,函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数 malloc 的区别只在于 calloc 会在 返回地址之前 把申请的空间的每个字节初始化为全0。 所以,使用malloc和calloc时,要注意区分它们的形参,和内存空间是否初始化。
2.2.1 calloc的用法#include#include int main() { int* ptr = NULL;//定义一个指针变量,用来接收malloc的返回值 ptr = (int*)calloc(num,sizeof(int)); //num是一个整数,sizeof(int)是整形变量占的字节数(4或2,取决于编译器) //即为 num 个大小为 int 的元素开辟一块空间 if(NULL != ptr)//判断ptr指针是否为空 { int i = 0; for(i=0; i 2.3realloc
这是realloc的函数原型,它的行参是一个指针变量(要调整内存的地址),加一个整数(调整后的内存大小),返回值是(调整后内存的起始)地址。
void* realloc (void* ptr, size_t size);它的作用是对已经开辟的动态内存做出调整,可以变大些(size变大),可以变小些(size变小),这取决于需求。
对于realloc做出的调整,会出现以下两种情况:
1.原内存之后有足够的空间来完成调整
在该位置完成内存调整,原有数据不变,返还原来的起始位置
2.原内存之后没有足够的空间来完成调整
在新的区域开辟内存,原有数据复制到新内存中,同时返回新内存的起始位置。
realloc内部解决办法,如图所示
3.这两种都是变大,但变小更好理解,就在原位置变小就可以了,就不用考虑另外再找位置开辟内存了。
但是,必须要注意的是,针对这三种情况,尤其是前两种,考虑,返回的地址我们到底应该那个变量来接收,是之前的指针,还是新用一个指针。若用之前的,那么若开辟失败,返回的是NULL,那么原来的数据不就丢失了吗?那么若用新的指针,是不是会增添过多变量,导致使用不便?
所以,我们应该这样做,一定要判断返回值是什么。
#includeint main() { int *ptr = (int*)malloc(100); if(ptr != NULL)//这是申请过程的判断 { //处理操作 } else { exit(EXIT_FAILURE);//申请失败,结束程序 } int*p = NULL; p = realloc(ptr, 1000); if(p != NULL)//判断一下,返回值是否为NULL,如果不是,我们就还用ptr; { ptr = p; //业务处理 } else { printf("开辟失败!");//如果是NULL,那就开辟失败了 } free(ptr); ptr=NULL; return 0; }
3.函数使用的注意事项
3.1函数返回值的判断及内存、指针的释放通过上面的解释,我们发现,对于动态内存的申请、修改,我们必须判断能否操作成果,再来就是要及时释放(归还)内存,释放指针,这是一种良好的编程习惯。
这种就是没有判断返回值,直接对指针使用,程序是无法运作的。
3.2对动态开辟空间的越界访问指针移动到了并非它所管理的内存空间,导致程序直接卡死,这种是很危险的,因为这个时候指针很有可能指向的是非法地址。
int i = 0; int *p = (int *)malloc(10*sizeof(int)); if(NULL == p) { exit(EXIT_FAILURE); } for(i=0; i<=10; i++) { *(p+i) = i;//当i是10的时候越界访问,只有10个空间,指针却移动了11次,越界访问。 } free(p);看到没有,直接卡死。
3.3对非动态开辟内存使用free释放int a = 10; int *p = &a; free(p);//ok?这样还是直接卡死
3.4使用free释放一块动态开辟内存的一部分int *p = (int *)malloc(100); p++; free(p);//p不再指向动态内存的起始位置不用怀疑,还是卡死。
3.5 对同一块动态内存多次释放int *p = (int *)malloc(100); free(p); free(p);//重复释放没错,它还是卡死
3.6 动态开辟内存忘记释放(内存泄漏)int *p = (int *)malloc(100); if(NULL != p) { *p = 20; }诶,这种情况不会卡死,但它很危险,虽然当整个程序结束时,内存会自动释放,但是当这个程序较大时,没有释放的内存就会被一直占着,浪费空间,而且当这种浪费开始叠加时,内存早晚会被占满,所以很危险。
所以,切记要及时释放动态内存。
动态内存的开辟比数组好用,它最大的优点就是按需开辟。
以上只是对知识点说明,下次将它们运用到具体的程序中。
以上就是,我对这些内容的理解。
声明:部分代码、文案(包括部分图片)来源于比特就业科中的课件
部分图片来源于作者本人



