一.为什么存在动态内存管理
我们常见的内存开辟方式:
int a = 20; //在栈空间上开辟四个字节
int arr[10] = { 0 }; //在栈空间上开辟40个字节
但是上的开辟空间方式有两个特点:
1.开辟的空间的大小是固定的;
2.数组在申明的时候,必须指定固定的长度,它所需的内存在编译时分配,有可能用不完造成浪费,也有可能后期不够用;
但是下在我们实际所需求中,不仅仅是上面的两种情况,有时候我们需要的空间大小在程序运行时才知道,那么上述方式开辟空间就不能够满足,所以有了动态内存开辟。
二.动态内存函数的介绍
必备知识:
内存大概的划分:
栈区: 主要存放局部变量,形式参数等等
堆区: 动态内存的开辟,malloc,free,calloc,realloc等等
静态区: 全局变量,静态变量等等
1. malloc函数(memory alloc 内存开辟)
函数介绍:
void* malloc(size_t size)
malloc函数可以向内存申请一块连续的空间,并返回指向这块空间的指针;
值得注意的是:
1. 如果开辟成功,将返回一个指向开辟好了的空间的指针;
如果开辟失败,将返回空指针NULL;
所以在开辟后需要判断是否开辟成功,检查返回值;
2. 函数返回的是空指针,malloc函数并不知道开辟空间的类型,所以在使用的时候需要自己决定(使用强制类型转换);
3.如果size为0时,malloc函数的行为是未定义的,取决于编译器;
4.malloc函数开辟的空间里面存放的是随机值(下面代码可验证);
5.malloc函数申请的空间,当程序退出时,还给操作系统,当程序不退出时,动态开辟的空间不会主动还给操作系统,需要用free函数来释放;
malloc函数使用举例代码:
int main() { int* p = (int *)malloc(40); //这里需要的是int*类型的指针,所以用()强制类型转换为int* if (p == NULL) { perror(malloc); //判断是否开辟成功 return 1; } int i = 0; for (i = 0; i < 10; i++) { printf("%d\n",*(p+i)); } free(p); p=NULL; //将p置为空指针,不要让p成为野指针 return 0; }
运行结果:
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
2.free(释放)
函数介绍:
void* free (void* p)
C语言提供的free函数是专门用来做动态内存的释放和回收的;
free是用来释放动态开辟的内存;
1.如果void* p所指向的内存不是动态开辟的,free函数的行为是未定义的;
2.如果void* p所指向的是NULL(空指针),则free函数什么都不做;
代码的示例:
int main() { int* p = (int *)malloc(40); //这里需要的是int*类型的指针,所以用()强制类型转换为int* if (p == NULL) { perror(malloc); //判断是否开辟成功 return 1; } int i = 0; for (i = 0; i < 10; i++) { printf("%d\n",*(p+i)); } free(p); //使用完该空间后释放 p=NULL; //将p置为空指针,不要让p成为野指针 return 0; }
3.calloc
函数介绍:
void* calloc (size_t num , size_t size)
功能:是为num个大小为size的元素开辟一块空间,并吃初始化为0;
与malloc函数类似,只是会将开辟的空间内容初始化为0;
代码示例:
int main() { int* p = (int* )calloc(40,sizeof(int)); if (p == NULL) { perror(calloc); return 1; } int i = 0; for (i = 0; i < 10; i++) { printf("%d ",*(p+i)); } free(p); p = NULL; return 0; }
运行结果:
0 0 0 0 0 0 0 0 0 0
4.realloc
realloc函数可以让内存管理更加灵活;
函数介绍:
viod* realloc (void* ptr,size_t size)
realloc函数可以调整我们申请的空间的大小;
其中ptr是要调整的内存地址;
size是调整之后的大小;
返回值是为调整之后的内存起始位置;
当void* ptr 为一个空指针时,功能和malloc一样;
值得注意的是:这个函数调整之后,可能将原来内存中的数据移动到新的空间;
分为两种情况;
1.原有空间的后面有足够大的空间时(即原有空间后面的剩余空间有所需增加空间那么大),就直接在原有空间后面开辟,并且返回指向原来空间的指针;
2.当原有的空间后面没有足够大的空间来增加空间,则需要重新找一个空间开辟,并且将原有空间的内容复制过去,然后释放旧的空间的内存,返回新的地址;
使用示例代码:
int main() { int* p =(int* )malloc(40); if (p == NULL) { perror("malloc"); return 1; } int i = 0; for (i = 0; i < 10; i++) { p[i] = i+1; } int* ret = realloc(p,80); { if (ret == NULL) { perror("realloc"); return 1; } else { p = ret; } } for (i = 0; i < 20; i++) { printf("%d ",*(p+i)); } free(p); p = NULL; return 0; }
运行结果:
1 2 3 4 5 6 7 8 9 10 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451
常见的动态内存管理的错误
1.对NULL指针进行解引用操作
void test
{
int *p = (int *)malloc(INT_MAX);
*p = 20; //如果p是空指针就有问题;
free(p);
}
当用内存函数开辟好空间后,不检查是否开辟成功就直接使用;
2.对动态开辟的空间越界访问
int* p = (int*)malloc(40);
int i = 0;
for(i=0;i<12;i++) // i=10,11,就越界访问了
{
printf("%d ",*(p+i));
}
3.对非动态开辟的空间使用free
int a = 10;
int* p = &a;
free(p); // 错误的
4.使用free释放一块动态开辟的内存中的一部分
int* p=(int *)malloc(40);
p++;
free(p); //p++后,已经不是指向的原开辟的内存,而是指向的一部分;
5.对同一块内存多次释放
int* p = (int*)malloc(40);
free(p);
free(p);
6.动态内存开辟忘记释放,造成内存泄漏
void test()
{
int * p =(int*)mmalloc(40)
if(NULL!=p)
{
*p=20;
}
} //使用完后忘记释放,出函数p销毁,但是40个字节的空间还在
int main()
{
test();
while(1); //死循环,程序不结束,40个字节永远用不到
}