为什么存在动态内存管理?
int a[20];
char a = 'foreb';
这是我们已经熟练掌握的内存开辟方式,但这种方式开辟的空间有以下特点:
1.空间的大小是固定的
2.申请数组时必须指定其大小
但往往我们会遇见这样的问题:所需要空间的大小只有在程序运行时才会知道。那刚才那种方式肯定行不通了,因为那方式不支持数组元素数目是变量,只能是常量。数组元素数目为常量会导致两个问题:
1.申请过多,浪费资源
2.申请过少,不够使用
那么此时为了解决这个问题,于是就有了动态内存管理。
动态内存函数介绍
malloc
C语言提供的动态内存开辟函数:
void* malloc(size_t size)
//该函数向内存申请一块 连续可用 的空间,返回指向该空间的指针
a. 如果开辟成功,malloc
返回一个指向某块空间的指针
b. 如果开辟失败,malloc
返回NULL指针
c. 因为返回值类型为void*
,所以malloc
并不知道自己开辟空间的类型,怎么使用取决于使用者
d. 当输入参数size为0时,malloc
的行为在标准中未定义,取决于编译器
e. malloc
申请的空间是在堆区申请的。因为在内存地址空间栈区和堆区相向而生,所以malloc
申请的空间大小也是有上界的。
f. 程序员用malloc
申请的空间,要由程序员释放,否则会发生内存泄漏(内存泄漏问题只会在引起内存泄漏问题的程序运行期间存在,一旦该程序结束运行,内存泄漏问题会消失【操作系统强制回收】)
g. malloc
是个函数,所以其申请空间的行为只会在程序运行期间发生,换句话说,堆空间的大小只能在程序运行期间得到确定
h. malloc
实际申请的空间会比输入的size
多一些,多出来的部分用来记录【这个空间有多大,起始地址在哪,啥时申请的】一些这样的信息,可以认为是被申请空间的属性,就像买零食,零食的外包装上的【配料表,能量表,生产日期】这类一样。
介绍完动态开辟,f中提到的释放也该上场了:free
free
void* free(void* ptr)
//free用来释放动态开辟的内存
a. 若ptr指向的空间并不是动态开辟的,则free
行为是未定义的
b. 若ptr是NULL指针,则free
什么也不做
c. 可以注意到,free
的输入参数只有一个ptr,仅仅是一个指向申请空间的指针,那free
怎么知道这块空间多大?不用担心,这些信息,由malloc
实际申请多出来的那部分空间提供。
d. malloc
和free
都声明在stdlib.h
头文件中。
举个栗子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num = 0;
int* ptr = NULL; //NULL赋初值【避免ptr成为野指针】
printf("请输入想要申请的空间大小(以int为例)# ");
scanf("%d",&num); //num在程序运行期间由用户输入,而不是提前被定下大小,动态的一种体现
ptr = (int*)malloc(num * sizeof(int));//malloc申请4*num字节的空间
if (NULL != ptr) { //【必须检查是否申请成功】
int i = 0;
for (i = 0; i < num; i++) {
*(ptr + i) = 0; //申请的空间存放num个0(int)
}
}
free(ptr); //释放动态申请的空间 【必须有释放步骤,否则有内存泄漏风险】
ptr = NULL; //重新使ptr指向NULL,否则ptr会成为野指针 【必须要有,野指针危害不小】
return 0;
}
calloc
calloc
也是C语言提供的内存管理函数
void* calloc(size_t num,size_t size)
//calloc的作用是开辟num个大小为size的空间,并将所申请的num*size字节的空间初始化为0
calloc
没什么说的,就是malloc
的基础加上初始化步骤。存在即合理,虽然calloc
就比malloc
多一个初始化,但也有其适合的使用场景,比如你要申请一块初始内容已知的空间,就可以用calloc
。
realloc
void* realloc(void* ptr,size_t size)
//realloc使内存管理更加灵活
为什么会更加灵活?试想,malloc和calloc虽然实现了动态内存申请,但我们有没有想到,在这两个函数申请仍会发生申请过多/过少的情况?它们并没有解决这两个问题,只是解决了我们不必在编程时就确定所要申请空间大小而已。
realloc
就是来解决这两个问题的!
先介绍realloc的一些情况:
a. ptr是要调整的内存地址
b. size为调整之后申请空间的大小
c. 返回值是调整大小之后空间的内存起始地址
d. 该函数调整原内存空间大小之后,还会将原来内存的数据移动到新空间里
e. realloc在调整内存空间时存在两种情况:
e1. 原有空间之后有足够大的空间(扩容)
e2. 原有空间之后没有足够大的空间(扩容)
e3. 减容没什么好说的,直接调用就行了
?? 为什么d中会出现数据转移的情况,原因在于e
e1. 有足够大空间意味着要扩充的空间(其大小为输入大小-原空间大小)只需续在原空间后面就行了
e2. 为什么会发生原有空间之后空间不够大呢?原因在于不只你一个程序在运行啊,你申请了空间,那么就有可能其他程序在你后面也申请了空间,导致你后面空间不够大了,这时你该怎么办?你只能在别的程序后面重新申请一块本次输入大小的空间了。为什么?因为这些函数只有一个返回值:一个指针!一个指针不能把两个不连续的地址让你串起来用。所以只能重新申请一块本次输入大小的空间。好了,申请好了,完事了?原空间可是有数据的!你要是直接返回新空间地址,就会丢失数据,所以会发生数据转移。数据转移完成后,才能将新空间地址返回。
【warning】:realloc
、malloc
、calloc
用完后都要被free
的,都要被检查是否申请成功的,否则各种错误让你烦不胜烦,尤其realloc
,返回值不要先赋给原空间的指针!,先检查是否成功!
栗子!
int main()
{
int num = 0;
int* ptr = NULL; //NULL赋初值【避免ptr成为野指针】
printf("请输入想要申请的空间大小(以int为例)# ");
scanf("%d",&num); //num在程序运行期间由用户输入,而不是提前被定下大小,动态的一种体现
ptr = (int*)malloc(num * sizeof(int));//malloc申请4*num字节的空间
if (NULL != ptr) { //【必须检查是否申请成功】
//业务处理
}
ptr = realloc(ptr, 1000);//将申请空间扩充到1000个字节
//该行为严重要摒弃!申请失败就凉凉了,【空间没了!数据没了!】
return 0;
}
正确行为:
int main()
{
int num = 0;
int* ptr = NULL; //NULL赋初值【避免ptr成为野指针】
printf("请输入想要申请的空间大小(以int为例)# ");
scanf("%d",&num); //num在程序运行期间由用户输入,而不是提前被定下大小,动态的一种体现
ptr = (int*)malloc(num * sizeof(int));//malloc申请4*num字节的空间
if (NULL != ptr) { //【必须检查是否申请成功】
//业务处理
}
//空间不够了,扩容
int* p = NULL;
p = realloc(ptr, 1000);//将申请空间扩充到1000个字节
if (p != NULL) {//申请成功再把ptr指向新空间!
ptr = p;
}
//继续业务处理
free(ptr);//使用结束,释放空间
return 0;
}
_alloc
void * _alloca(size_t size);
_alloca()
在栈上开辟空间,不允许程序员手动free()释放,函数运行完系统自动回收