C语言 动态内存管理 柔性数组
蒋灵瑜的流水账 人气:0一、malloc
这个函数向堆区申请一块连续的空间,并返回这块内存的地址。
int main() { int* p = (int*)malloc(40); if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } free(p); p = NULL; return 0; }
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
如果参数 size为0,malloc的行为是标准是未定义的,取决于编译器。
二、free
free函数用来释放动态开辟的内存。free的指针必须是指向动态内存空间的起始地址。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
三、calloc
num:要动态开辟的元素个数
size:每个元素的大小
calloc=malloc+memset
calloc和malloc的区别就在于calloc会将动态内存开辟的空间全部初始化为0
四、realloc
ptr:需要扩容的原始地址
size:扩容后的总大小
1、realloc在扩容时的情况
1、扩容时后方空间足够,将在后方继续扩容,返回原始地址
2、扩容时后方空间已被使用,将在新的一块区域进行扩容,并将原始数据拷贝至新的区域,free原始地址指向的内存,并返回内存块的地址。
3、空间不足,无法扩容,返回空指针
2、realloc也能实现malloc功能
int main() { int* p = (int*)realloc(NULL, 40); return 0; }
realloc在当malloc使用时,第一个参数传空指针即可。
五、使用动态内存的常见错误
1、free空指针
void text() { int* p = (int*)realloc(NULL, 40); if (p == NULL)//判断p是否为空指针 { return; } *p = 20; }
如果开辟空间后,没有判断指针p是否是空指针,如果动态内存开辟失败,那么*p将会解引用空指针。
2、对动态开辟的空间越界访问
void text() { int* p = (int*)realloc(NULL, 40); int* m = p; if (p == NULL) { return; } for (int i = 0; i <= 10; i++) { p[i] = i;//越界 p++; } }
3、对非动态开辟内容free
void text() { int arr[] = {1,2,3}; int* p = arr; free(p);//不能对非动态开辟的空间进行free }
4、只free动态开辟空间的一部分
void text() { int* p = (int*)malloc(40); p++; free(p);//p不再指向动态开辟空间的起始位置 }
实际写代码的时候还是得多注意这种情况,指针p在使用的过程中已经不再是初始位置,free(p)会崩溃。
5、对同一块内存多次free
void text() { int* p = (int*)realloc(NULL, 40); free(p); //p = NULL; free(p); }
free(p)后及时将p置为空指针,哪怕p被多次free也没问题。
6、动态内存空间忘记释放(内存泄漏)
void text() { int* p = (int*)realloc(NULL, 40); int a = 0; scanf("%d", &a); if (a == 5) return; free(p); p = NULL; }
当函数提前终止,没有机会走到free,可能会造成内存泄漏
另一种可能是调用了动态开辟内存的函数,使用者未在外部进行free
六、柔性数组
1、柔性数组的概念
C99中,结构体中最后一个成员变量可以是未知大小的数组
struct S { int a; int arr[0];//这里的arr[0]代表未知大小的数组 }; //或者如下写法: struct S { int a; int arr[]; };
2、柔性数组的特点
结构体中的柔性数组成员前面必须至少一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
3、柔性数组的使用场景
场景:将结构体中所有的成员变量全部在堆区开辟
使用柔性数组:
struct S { int a; int arr[0]; }; int main() { struct S s; struct S* ps = &s; ps = (struct S*)realloc(NULL,sizeof(s) + 40);//首次开辟空间,传NULL if (ps == NULL) { exit(-1); } free(ps); ps = NULL; return 0; }
注意此处的realloc第一个参数传入的是NULL而不是&s,传入&s会崩溃,是因为这是第一次动态内存开辟,此处传入NULL相当于使用malloc
完成开辟后s在内存中的存储如下图:
使用常规结构体:
struct S { int a; int* parr; }; int main() { struct S* p = (struct S*)malloc(sizeof(struct S));//对结构体动态开辟空间 if (p == NULL) { exit(-1); } int* tmp= (int*)malloc(sizeof(int) * 2);//在堆区动态开辟一块空间 if (tmp == NULL) { exit(-1); } p->parr = tmp; free(p->parr);//释放时,需要先释放p->parr指向的空间 p->parr = NULL; free(p);//再将结构体指针p指向的空间释放 p = NULL; return 0; }
完成开辟后s在内存中的存储如下图:
4、柔性数组的优点
1、在上述条件下,使用柔性数组方便动态内存释放。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,可能会造成内存泄漏。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存给释放掉。
2、连续的内存有益于提高访问速度,也有益于减少内存碎片。
加载全部内容