1.为什么存在动态内存管理
在我们之前的学习里,我们已经掌握了开辟内存的方式
int val = 20;//在栈空间开辟四个字节 char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式存在很大的局限性:
1.开辟的空间大小是固定的;
2.数组在申明的时候必须指定数组的长度,所需要的内存大小在编译时分配。
但是,对于空间的需求不止于此,有时候我们只有在程序运行起来以后才能知道所需空间大小,那数组的编译时开辟空间就不能满足了,因此我们引入了动态内存开辟的方法。
2.动态内存函数介绍
1.malloc
void*malloc(size_tsize);
malloc向内存申请一块连续可用的空间,并返回这块空间的指针。
- 如果开辟成功,返回指向这块空间的指针
- 如果开辟失败,返回NULL,因此使用malloc以后要做好检查
- 返回值的类型是void*,使用前要强转
- 如果参数size传的是0,这是C语言标准未定义的行为,由编译器决定
2.calloc
c语言还提供了一个函数叫calloc,也用来动态内存分配
void*calloc(size_tnum,size_tsize);
是用来为开辟num个大小为size的空间,并且把空间内的每个字节初始化为0,与malloc的区别就是是否会初始化申请的内存空间。
3.realloc
realloc函数的出现,让动态内存开辟更加灵活,有时候我们在使用动态开辟的空间,会发现空间小了或者大了。为了合理的使用内存,我们引入了realloc函数。
void*realloc(void*memblock,size_tsize);
- 第一个参数是需要调整的内存地址
- 第二个参数是调整后的新大小,
- 返回值是调整后的内存的起始位置
- realloc函数调整内存空间有两种情况
- 情况一:原有空间之后有足够大的空间,此时realloc函数会在该空间后面直接追加空间,原有数据不受影响
- 情况二:原有空间之后没有足够大的空间,此时realloc函数会再开辟一个足够大的空间,并将原有数据拷贝到新的空间,然后释放原有空间。
4.free
上述的三个动态内存开辟函数都是在堆区上开辟空间,那么空间使用后是需要释放的,否则就会造成内存泄漏,因此我们用free函数释放动态开辟的内存空间
void free( void *memblock );
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
3.常见的动态内存错误
1.对NULL指针的解引用操作
void test() { int *p = (int *)malloc(INT_MAX/4); *p = 20;//如果p的值是NULL,就会有问题 free(p); }
上面的代码没有检验是否开辟成功,判断p是否为空指针,直接解引用,如果p的值为NULL,就会有问题
2.对动态开辟空间越界访问
void test() { 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的时候越界访问 } free(p); }
我们只开辟了10个整形空间,当i = 10的时候,会访问到11个整形空间,就会越界访问。
3.对非动态开辟内存使用free释放
void test() { int a = 10; int *p = &a; free(p); }
变量a是在栈区开辟的,而动态内存是在堆区开辟的,因此变量a不能用free释放,这是未定义的行为。
4.使用free释放一块动态开辟内存的一部分
void test() { int *p = (int *)malloc(100); p++; free(p);//p不再指向动态内存的起始位置 }
此时,p已经不再指向动态开辟的内存的起始位置,不能用free释放。