前言:现在我们掌握的内存开辟方式开辟的空间都是固定的,但是对于空间的需求,有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了。 这时候就要使用动态内存开辟了。
一、动态内存函数的介绍
1.1malloc和free函数
C语言提供的动态内存开辟的函数malloc:
void* malloc (size_t size);
malloc函数的功能:这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
参数:要申请内存块的大小,单位是字节。
返回值:内存申请成功时,返回分配的内存块的起始地址。不确定返回值的类型,定义成void*。
malloc函数的使用:
#include <stdio.h> #include <stdlib.h> int main() { //开辟一个有10个元素的数组 int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); return 1; } int i = 0; for (i = 0; i < 10; i++) { printf("%d\n", *(p + i)); } return 0; }
注意:
1.空间可能开辟失败,如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
2.如果参数 size为0,malloc的行为是标准是未定义的,取决于编译器。
3.malloc申请到空间后,直接返回这块空间的起始地址,不会初始化空间的内容。
malloc申请的空间在程序没有退出时,时不会主动还给操作系统的,只有退出程序,才会还给操作系统,所以我们要使用free函数来释放。
C语言提供了函数free,专门是用来做动态内存的释放和回收的:
void free (void* ptr);
free函数的使用:
int main() { //开辟一个有10个元素的数组 int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); return 1; } free(p); p = NULL; return 0; }
注意:
使用free函数释放空间后,p指向这块空间没用了,就变成野指针,所以我们在释放空间后,要将p置为空指针。
如果参数 ptr 是NULL指针,则函数什么事都不做。free只能释放动态开辟的内存。如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
1. int a=10; 2. int* p=&a; 3. free(p); //err 4. p=NULL:
1.2calloc函数
C语言还提供了一个函数叫calloc , calloc 函数也用来动态内存分配。
void* calloc (size_t num , size_t size);
calloc函数的功能:为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
参数:
num:要分配的元素数
size:每个元素的大小
calloc函数的使用:
int main() { int* p = (int*)calloc(10, 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; }
要的空间太大就会开辟失败:
1.3realloc函数
C语言提供的动态内存开辟的函数realloc:
void* realloc (void* ptr, size_t size);
参数:
ptr:要调整的内存地址,以是一个空指针
size:调整之后新大小
返回值:调整之后的内存起始位置(该内存块可能与旧位置相同,也可能是新位置)
realloc函数的使用:
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* ptr = (int*)realloc(p, 80); if (ptr != NULL) { p = ptr; } else { perror("realloc"); } //打印数据 for (i = 0; i < 20; i++) { printf("%d ", p[i]); } free(p); p=NULL; return 0; }
注意:
realloc有开辟失败的可能,如果用p接收,返回空指针,malloc开辟的空间无法释放,会造成数据泄露,所以我们创建一个ptr来就收realloc返回的地址,如果返回的不是空指针就赋值给p。
realloc在调整内存空间的两种情况:
1.原有空间之后有足够大的空间
要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
2.原有空间之后没有足够多的空间
在堆空间上另找一个合适大小的连续空间来使用,把旧的数据拷贝到新的空间中。会将旧的空间释放掉,返回新的地址 。
二、常见的动态内存的错误
2.1对NULL指针的解引用操作
void test() { int *p = (int *)malloc(INT_MAX/4); *p = 20;//如果p的值是NULL,就会有问题 free(p); }
如果空间开辟失败返回空指针,我们对空指针解引用,程序就会出现错误。所以我们要判断返回的是否为空指针。
代码修改:
int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); return 1; } *p=20; free(p);
2.2对动态开辟空间的越界访问
只有10个整型的空间,确访问了20个整型的空间,越界访问。
2.3对非动态开辟内存使用free释放
1. int a=10; 2. int* p=&a; 3. free(p); //err 4. p=NULL:
2.4 使用free释放一块动态开辟内存的一部分
循环结束后,p指向的不是起始位置。无法释放一部分空间,必须从起始位置释放。
2.5对同一块动态内存多次释放
1. void test() 2. { 3. int *p = (int *)malloc(100); 4. free(p); 5. free(p);//重复释放 6. }
2.6动态开辟内存忘记释放(内存泄漏)
void test() { int *p = (int *)malloc(100); if(NULL != p) { *p = 20; } } int main() { test(); while(1); return 0; }
动态申请的内存空间不会因为出了作用域自动销毁。
只有两种销毁方式:
1.free
2.程序结束