-
我们已经掌握的内存开辟方式有:
int i = 20; //在栈空间开辟4个字节 char arr[10] = { 0 }; //在栈空间开辟10个字节的连续空间 -
但上述的开辟空间的方式有两个特点:
- 空间开辟的大小是固定的
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配
-
但是对于空间的需求,不仅仅是上述的情况。有时我们需要的空间大小在程序运行的时候才能知道,那数组在编译时开辟空间的方式 就不能满足了,所以这个时候只能试试动态内存开辟了
分配内存空间函数 malloc()为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收待用,为有效的利用内存资源提供了手段
- 调用形式:void* malloc (size_t size);
- 功能:分配内存块
- 在内存的动态存储区中开辟一块长度为“ size ”字节的连续可以的空间,并返回指向这块空间的指针。
- 函数的返回值为该区域的首地址(指向开辟好的空间的首地址)
- 如果开辟空间失败,则函数的返回值为一个NULL指针,因此malloc()的返回值一定要做检查
- 返回值的类型是void*,所以malloc()函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定,所以需要我们将void*强制类型转换为我们需要的指针类型
- “size”是一个无符号数,如果参数size为0,则malloc()函数的行为标准为定义的,取决于编译器(size_t表示一个无符号整型)
- 调用形式:void free (void* ptr);
- 功能:
- 释放 ptr 所指向的一块内存空间,ptr 是一个任意类型的指针变量,它指向的被释放区域的首地址。
- 被释放区域应是由malloc()和calloc()函数所分配的区域
- 如果参数ptr指向的空间不是动态开辟的,那free()函数的行为是未定义的
- 如果参数ptr是NULL指针,则函数什么也不做
- 使用free() 函数将内存释放之后,不会将ptr这个指针置为空指针,所以为了非法访问内存,需要我们手动将ptr置为空指针
即:ptr = NULL; - free()函数和malloc()、calloc()函数成对出现
例:
#include分配内存空间函数 calloc()#include #include int main() { struct student //定义了结构 student { int num; char name[20]; char sex; float score; }*ps; //定义了结构指针变量ps ps = (struct student*)malloc(sizeof(struct student));//分配了一块student类型的内存空间,并把首地址赋给ps,使ps指向该区域,注意使用malloc()函数的时候应该将void*强制类型转换为我们需要的指针类型(struct student*) ps->num = 102; strcpy(ps->name, "张三"); ps->sex = 'M'; ps->score = 67.5; //以ps为结构指针变量对各个成员赋值 printf("num = %dnname = %sn", ps->num, ps->name); printf("sex = %cnscore = %fn", ps->sex, ps->score); free(ps); //用free()函数释放ps指向的内存空间 ps = NULL; //将ps这个指针置为空指针,避免非法访问 return 0; }//整个程序包含了申请内存空间,使用内存空间,释放内存空间3个步骤,实现了存储空间的内存分配
-
调用形式:void* calloc (size_t num, size_t size);
-
功能:为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
-
calloc()和 malloc()的区别在于:calloc()`函数会在返回地址之前把开辟的连续区域的每个字节初始化为0
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-woPEf0L5-1659065555159)(C:Users何锦AppDataRoamingTyporatypora-user-imagesimage-20220728161809391.png)]
realloc()函数的出现让动态内存管理更加灵活
有时候我们发现开辟的内存区域或大或小,会对内存的大小做灵活的调整,这时候就会用到realloc()函数
-
调用形式:void* realloc (void* ptr, size_t size);
-
ptr是需要调整的内存地址,是一个任意类型的指针变量;size为调整之后新的大小;返回值为调整之后的内存起始位置
-
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移到新的空间
-
realloc()函数在调整内存空间时存在两种情况:
- 原有空间之后有足够大的空间,要扩展内存就直接在原有内存之后直接追加空间,函数返回旧的地址
- 原有空间之后没有足够大的空间,那么就会在堆空间上另找一个合适大小的连续空间来使用,原来的空间就会被释放掉,函数返回的是一个新的地址
-
值得注意的是:对于返回的地址,我们是用原来的指针变量来接收还是另外定义一个指针变量来接收?
-
如果realloc()函数在堆区找不到合适的空间来调整大小,就会返回空指针NULL,这样原来的指针ptr就会丢失
-
所以最好的办法就是定义一个临时的指针变量ptr1
int* ptr1 =realloc(ptr, 20); //定义一个临时指针变量ptr1 if(ptr1 != NULL) //判断是否调整成功 { ptr = ptr1 }
-
-
realloc()函数和malloc()函数:
int* p = (int*)realloc(NULL,40); int* p = (int*)malloc(40);//这两段代码意义一致
-
对NULL指针的解引用操作
#include
int main() { int* p = (int*)malloc(1000000000); //开辟的空间过大导致开辟失败,malloc()函数返回一个空指针,所以对malloc()函数的返回值要进行NULL判断, int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; //p是一个空指针,会导致非法访问 } printf("%dn", *(p+i)); free(p); p = NULL; return 0; }//编译结果:-842150451 明显不对 -
对动态开辟空间的越界访问
#include
#include int main() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (p != NULL) { for (i = 0; i <= 10; i++) { *(p + 1) = i; //当 i 是10的时候越界访问 } free(p); p = NULL; } return 0; }//编译结果:无结果 -
对非动态内存使用free()释放
int main() { int a; int* p = &a; free(p); //free()函数只能释放位于堆区的动态内存,而不能释放位于栈区的非动态内存函数 return 0; } -
使用free()释放一块动态开辟内存的一部分
int main() { int* p = malloc(10 * sizeof(int)); if (p == NULL) { return 1; } int i = 0; for (i = 0; i < 5; i++) { *p++ = i; } //经过循环之后,p指向的不再是原来的位置,找不到原来p所指向的位置了 free(p); //free()函数只释放了动态内存的一部分 p = NULL; return 0; } -
对同一块动态内存多次释放
int main() { int* p = (int*)malloc(100); free(p); free(p); //重复释放 return 0; } -
动态开辟内存忘记释放(内存泄漏)
void test() { int* p = (int*)malloc(100); if(P != NULL) { *p = 20; }//没有释放不再使用的动态开辟的空间会造成内存泄漏 } int main() { test(); return 0; }动态内存回收的两种方式:free()函数回收;程序结束回收。
例1:
#include#include void GetMemory(char* p) { p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } int main() { Test(); return 0; } //编译不成功
- str传给GetMemory函数是值传递,GetMemory函数的形参p是str的一份临时拷贝,在GetMemory函数内部动态申请空间的地址放在p中,不会影响外面的str,所以GetMemory函数返回之后,str依然是空指针;
- 当GetMemory函数返回之后,形参p销毁,使得动态开辟的100个字节存在内存泄漏,无法释放
例2:
#includechar* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; }
- GetMemoy函数内部创建的数组是在栈区上创建的,一旦出了函数,p数组的空间就还给了操作系统,那么函数返回的地址是没有实际意义的,如果通过返回的地址去访问内存就是非法访问内存
-
C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组的成员
-
struct stu { int i; int a[]; //在有些编译器下可以写成 int a[0]; }stu1;
-
-
柔性数组的特点
-
结构中的柔性数组成员前面必须至少一个其他成员
-
sizeof返回的这种结构大小不包括柔性数组的大小
struct stu { int i; int a[]; }stu1; int main() { printf("%d", sizeof(type_a)); return 0; } //编译结果:4 -
柔性数组的大小是未知的
-
包含柔性数组成员的结构用malloc()函数进行内存动态分配时,分配的内存应该大于结构的大小,以适应柔性数组的预期大小
struct stu { int i; int a[]; }stu1; int main() { //希望柔性数组为10个整型的大小 struct stu* ps = (struct stu*)malloc(sizeof(stu1) + 10 * sizeof(int)); ps->i = 10; int j = 0; for (j = 0; j < 10; j++) { ps->a[j] = j; } //增加空间 struct stu* str = (struct stu*)realloc(ps, sizeof(stu1) + sizeof(int) * 10); if (str != NULL) { ps = str; } //使用 //释放 free(ps); return 0; }
-
-
柔性数组的优势
-
首先我们看一段代码:
struct stu { int i; int* pa; }stu1; int main() { struct stu* ps = (struct stu*)malloc(sizeof(struct stu)); if (ps == NULL) { return 1; } ps->i = 10; ps->pa = (int*)malloc(10 * (sizeof(int))); if (ps->pa == NULL) { return 1; } int j = 0; for (j = 0; j < 10; j++) { ps->pa[j] = j; } //增加 int* str = (int*)realloc(ps->pa, sizeof(int) * 20); if (str != NULL) { ps = str; } //释放 free(ps->pa); ps->pa = NULL; free(ps); ps = NULL; return 0; }我们发现,上面这一段代码完全可以替代柔性数组的功能,但哪种方法好呢?
-
柔性数组的优势:
- 方便内存释放:如果我们的代码时在一个给别人使用的函数中,在里面做了二次内存分配,并把整个结构体返回,那么free()函数释放结构体,但结构体内部的成员也需要释放,而使用柔性数组后,只需要一次free()函数即可释放内存
- 有利于访问速度
- 有利于减少内存碎片
-



