引言:
学 C 的时候,我一度觉得
malloc挺多余——数组不是挺好用吗?直到写了个小练习:让用户输入一串数字再排序。写完发现卡住了,数组长度写多少?写 10,用户输入 11 个就崩;写 1000,又嫌浪费,万一输入 1001 个呢。
关键是,到底有几个数字,我写代码的时候根本不知道,得等程序跑起来才清楚。
malloc解决的就是这个:运行时再决定要多少内存,代价是这块内存从此归你管——用完得free,不能越界,free完也不能再用。动态内存管理也是学好数据结构的前置知识点。
这篇就来讲清楚这几件事。
目录
1.为什么要有动态内存分配
2.malloc和free
2.1malloc
2.2free
3.开辟成功和失败的案例
3.calloc和realloc
3.1calloc
3.3realloc
1.为什么要有动态内存分配
我们已经掌握的的内存开辟方式有:
int val = 20; //在栈空间上开辟四个字节 char arr[10] = { 0 }; //在栈空间上开辟10个字节的连续空间但是上述开辟空间的方式有两个特点:
1.空间开辟大小是固定的
2.数组在申明的时候,必须指定数组的长度,数组空间⼀旦确定了大小不能调整
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运⾏的时候才能知 道,那数组的编译时开辟空间的方式就不能满足了。
C语言引入了动态内存开辟,让程序自己可以申请和释放空间,就比较灵活了。
2.malloc和free
2.1malloc
C语言为我们提供了一个动态内存开辟的函数
void* malloc (size_t size);
功能:向内存的堆区申请一块连续可用的空间,并返回指向这块空间的起始地址。
1. 如果开辟成功,则返回这块空间的起始地址。
2.如果开辟失败,则返回⼀个 • 返回值的类型是 NULL 指针,因此malloc的返回值⼀定要做检查。
3.返回值的类型是void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者自己来决定。
4. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器
2.2free
C语言提供了一个用于做动态内存的释放和回收的函数:free
void free (void* ptr);free函数用来释放动态开辟的内存,就像一个图书馆,不能一直借书但是从来不还书,这样会出问题的,malloc和free都声明在stdilb.h头文件中。
如果参数ptr指向的空间不是动态开辟的,那函数free的行为是未定义的。
如果参数ptr是NULL指针,则函数什么都不需要做。
3.开辟成功和失败的案例
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(20);//在堆区上申请20个字节的空间 if (p == NULL) { perror("use malloc"); return 1; } //使用空间 int i = 0; for (i = 0; i < 5; i++) { p[i] = i + 1; } //释放内存 free(p); return 0; }当程序指向完free这一行,我们使用malloc开辟的内存就被回收掉了,但是当我们把地址还掉之后,p还指向原来的地址,此时的p就是野指针,这个时候给他一个空指针可以了,就像给野狗套上铁链子。
当我们把编译器环境从x64改成x86,并且在malloc中输入特别大的数字,动态内存的开辟就有可能失败,错误信通过perror函数打印出来。
3.calloc和realloc
3.1calloc
C语言还提供了一个函数calloc,calloc也能用来动态内存分配
void* calloc (size_t num, size_t size);函数的功能是为num个⼤⼩为size的元素开辟⼀块空间,并且把空间的每个字节初始化为0
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0。
int main() { int* p = (int*)calloc(5, sizeof(int)); if (p == NULL) { perror("use malloc"); return 1; } //使用空间 int i = 0; for (i = 0; i < 5; i++) { p[i] = i + 1; } //释放内存 free(p); p = NULL; return 0; }malloc和calloc仅仅在初始化上有区别,其他方面是没有区别的。
3.3realloc
realloc的出现使得动态内存管理更加灵活
有时我们会觉得内存过小不够用,有时候内存过大又会产生浪费,为了更合理的使用内存,我们会对内存的大小做灵活调整,realloc就可以做到对动态开辟内存大小的调整。
void* realloc (void* ptr, size_t size);功能:
重新调整之前分配的内存块的大小,它可以在不丢失原有数据的情况下,扩大或缩小动态分配的内存块。
参数:
ptr是要调整的内存空间的起始地址,如果ptr是NULl指针,realloc函数的功能类似于malloc函数。
size调整之后新大小,单位是字节。
返回值:
成功:返回一个指向重新分配的内存块的void*类型指针。这个指针可能与原来的指针不同。
失败:如果内存重新分配失败,返回 NULL,并且原来的内存块保持不变。
int main() { int* p = (int*)calloc(5, sizeof(int)); if (p == NULL) { perror("use malloc"); return 1; } //使用空间 int i = 0; for (i = 0; i < 5; i++) { p[i] = i + 1; } //希望空间能放10个整形 int* ptr = realloc(p, 10 * sizeof(int)); if (ptr == NULL) { perror("use realloc"); return 1; } else { p = ptr;//继续使用p来维护空间 } //继续使用 for (i = 5; i < 10; i++) { p[i] = i + 1; } free(p); p = NULL; return 0; }注意:这里不能直接使用p来接收realloc的结果,因为p本身就指向了一块空间,如果realloc开辟失败,那么p直接被置为NULL了,所以我们可以先创建一个ptr来接收,待确认空间开辟成功,再让p接收。
使用realloc调整空间存在两种情况:
1.原有的空间后有足够大的空间,此时拓展内存就直接在原有内存之后直接追加内存,原来空间的数据不发生变化
2.当原有空间之后没有足够多的空间时,扩展的方法是在堆空间另找一个合适大小连续的空间来使用,并返回新的地址,原来空间中存放的数据会复制过来,随后旧空间就释放掉了。
旧空间:”我免费了“(free)
因为篇幅原因动态内存暂时就写到这,下一篇会把常见的动态内存错误,一些经典的面试题,柔性数组等知识点讲清楚。
再次感谢各位码友的观看,祝我们都能实现自己的理想!