进阶C语言-动态内存管理
动态内存管理
- 🎈1.为什么存在动态内存分配
- 🎈2.动态内存函数的介绍
- 🔭2.1malloc和free函数
- 🔭2.2calloc函数
- 🔭2.3realloc函数
- 🎈3.常见的动态内存错误
- 🔭3.1对NULL指针的解引用操作
- 🔭3.2对动态开辟空间的越界访问
- 🔭3.3对非动态开辟空间内存使用free释放
- 🔭3.4使用free释放一块动态开辟内存的一部分
- 🔭3.5对同一块动态内存多次释放
- 🔭3.6动态开辟内存忘记释放(内存泄漏)
- 🎈4.几个经典的笔试题
- 🔭4.1题目一
- 🔭4.2题目二
- 🔭4.3题目三
- 🔭4.4题目四
- 🎈5.C/C++程序的内存开辟
- 🎈6.使用动态内存相关的知识改进通讯录
- 🎈7.柔性数组
- 🔭7.1柔性数组的特点
- 🔭7.2柔性数组的使用
- 🔭7.3柔性数组的优势
🎈1.为什么存在动态内存分配
✅截止目前,我们掌握的内存开辟的方式有:
int a = 10;//在栈空间上开辟4个字节 char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
- 空间开辟的大小是固定的。
- 数组在申明的时候,必须指定数组的长度,它所需的内存在编译时分配。
🔎但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道那数组的编译时开辟空间的方式就不能满足了。这个时候,我们就只能试试动态存开辟!
🎈2.动态内存函数的介绍
🔭2.1malloc和free函数
🏆C语言提供了一个动态开辟内存的函数:
void *malloc(size_t size);
✅这个函数向内存申请一块连续可用的空间,并返回这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者来决定。
- 如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。
int main() { //申请一块空间,用来存放10个整型 int* p = (int*)malloc(10 * sizeof(int)); return 0; }
✅内存的存储:
📖注意: malloc和free都声明在stdlib.h的头文件中。
✅运行示例:
#include #include int main() { //申请一块空间,用来存放10个整型 int* p = (int*)malloc(10 * sizeof(int)); if (p == NULL) { perror("malloc"); return 1; } //使用 int i = 0; for (i = 0; i
🏆C语言提供了另外一个函数free,专门用来做动态内存的释放和回收:
void free(void *ptr);
free函数用来释放动态开辟的内存。
- 如果参数ptr指向的空间不是动态开辟的,则free函数的行为是未定义的。
- 如果参数ptr是NULL指针,则函数什么事都不做。
🌞malloc函数申请的空间,是怎么释放的呢?
- free释放-主动释放
- 程序退出后,malloc申请的空间,也会被操作系统回收。-被动回收
free(ptr);//释放ptr所指向的动态内存 ptr = NULL;
🔭2.2calloc函数
🔎malloc和calloc函数除了参数的区别,calloc函数申请好空间后,会将空间初始化0,但是malloc函数不会初始化。
🔭2.3realloc函数
✅该函数用于对我们已开辟动态空间大小的调整!
#include #include int main() { //申请一块空间,用来存放10个整型 int* p = (int*)malloc(10 * sizeof(int)); if (p == NULL) { perror("malloc"); return 1; } //使用 int i = 0; for (i = 0; i
🔎但是这种方法是有问题的,因为realloc开辟空间也可能会失败,失败的时候返回NULL!因此,如果这个时候,我们还用p来接收的话,那么我们原来有的10个字节的空间可能也丢失了。
//更改: //空间不够,希望调整空间为20个整型的空间 int* ptr = (int*)realloc(p, 20 * sizeof(int)); if (ptr != NULL) { p = ptr; }
✅realloc函数是如何工作的呢?
- 要扩展的内存就直接在原有内存之后追加空间,原来空间的数据不发生变化。
- 原有空间之后没有足够多的空间,扩展的方法就是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存空间。
🎈3.常见的动态内存错误
🔭3.1对NULL指针的解引用操作
void test() { int* p = (int*)malloc(INT_MAX / 4); *p = 20;//如果p的值是NULL,就会有问题 free(p); }
🔭3.2对动态开辟空间的越界访问
void test() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { exit(EXIT_FAILURE); } for (i = 0; i *(p + i) = i;//当i是10的时候越界访问 } free(p); } int a = 10; int* p = &a; free(p);//no } int* p = (int*)malloc(100); p++; free(p);//p不再指向动态内存的起始位置 } int* p = (int*)malloc(100); free(p); free(p);//重复释放 } int* p = (int*)malloc(100); if (NULL != p) { *p = 20; } } int main() { test(); while (1); } p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } *p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); free(str); str = NULL; } int main() { Test(); return 0; } char *p = (char*)malloc(100); return p; } void Test(void) { char* str = NULL; str = GetMemory(); strcpy(str, "hello world"); printf(str); free(str); str = NULL; } int main() { Test(); return 0; } char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; } *p = (char*)malloc(num); } void Test(void) { char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } int main() { Test(); return 0; } *p = (char*)malloc(num); } void Test(void) { char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); free(str); str = NULL; } int main() { Test(); return 0; } char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world"); printf(str); } } int main() { Test(); return 0; } char name[NAME_MAX]; int age; char sex[5]; char tele[12]; char addr[20]; }PInfo; //静态通讯录 //typedef struct Contact //{ // PInfo data[Max]; // int sz;//用于记录当前通讯录中存放了多少个人的信息 //}Contact; //动态通讯录的版本 typedef struct Contact { PInfo* data;//存放数据 int sz;//记录当前通讯录中存放的人的信息的个数 int capacity;//记录通讯录的容量 }Contact; //初始化通讯录 void InitContact(Contact* c); //增加联系人 void AddContact(Contact* c); //删除指定的联系人 void DelContact(Contact* c); //查找指定的联系人 void SearchContact(Contact* c); //修改指定联系人 void ModifyContact(Contact* c); //按照年龄排序 void AgeSortContact(Contact* c); //销毁通讯录 void DestroyContact(Contact *c); contact.c #define _CRT_SECURE_NO_WARNINGS 1 #include "contact.h" //静态的版本 //void InitContact(Contact *c) //{ // assert(c); // c-sz = 0; // memset(c->data, 0, sizeof(c->data)); //} void InitContact(Contact* c) { assert(c); c->sz = 0; c->capacity = DEFAULT_SZ; c->data = calloc(c->capacity, sizeof(PInfo)); if (c->data == NULL) { perror("InitContact->calloc"); return; } } //增容的函数可以单独封装 void CheckCapacity(Contact* c) { if (c->sz == c->capacity) { PInfo* ptr = (PInfo*)realloc(c->data, (c->capacity + 2) * sizeof(PInfo)); if (ptr != NULL) { c->data = ptr; c->capacity += 2; printf("增容成功!\n"); } else { perror("AddContact->realloc"); return; } } } //销毁通讯录 void DestroyContact(Contact* c) { free(c->data); c->data = NULL; c->sz = 0; c->capacity = 0; } void AddContact(Contact* c) { //首先要判断该通讯录是否已经满了 assert(c); //增加容量 CheckCapacity(c); if (c->sz == Max) { printf("通讯录已满,无法增加!\n"); return; } printf("请输入姓名:"); scanf("%s", c->data[c->sz].name); printf("请输入年龄:"); scanf("%d", &c->data[c->sz].age); printf("请输入性别:"); scanf("%s", c->data[c->sz].sex); printf("请输入电话:"); scanf("%s", c->data[c->sz].tele); printf("请输入地址:"); scanf("%s", c->data[c->sz].addr); c->sz++; printf("增加成功!\n"); } void ShowContact(const Contact* c) { assert(c); if (c->sz == 0) { printf("通讯录为空,无需打印!\n"); } int i = 0; printf("%-20s%-5s%-5s%-12s%-30s\n", "姓名", "年龄", "性别", "电话", "地址"); for (int i = 0; i sz; i++) { printf("%-20s%-5d%-5s%-12s%-30s\n", c->data[i].name, c->data[i].age, c->data[i].sex, c->data[i].tele, c->data[i].addr); } } int FindByName(Contact* c, char name[]) { assert(c); int i = 0; for (i = 0; i sz; i++) { if (strcmp(c->data[i].name, name) == 0) { return i; } } return -1;//找不到 } void DelContact(Contact* c) { char name[NAME_MAX]; assert(c); if (c->sz == 0) { printf("通讯录为空,无法删除!\n"); return; } printf("输入要删除人的姓名:"); scanf("%s", name); //找到姓名为name的人 int ret = FindByName(c, name); if (ret == -1) { printf("要删除的人不存在!\n"); return; } //删除这个人 int i = 0; for (i = ret; i sz - 1; i++) { c->data[i] = c->data[i + 1]; } c->sz--; printf("删除成功!\n"); } void SearchContact(Contact* c) { char name[NAME_MAX]; assert(c); printf("请输入要查找人的姓名:"); scanf("%s", name); int ret = FindByName(c, name); if (ret == -1) { printf("要查找的人不存在!\n"); return; } //若找到了,打印出相关信息 printf("%-20s%-5s%-5s%-12s%-30s\n", "姓名", "年龄", "性别", "电话", "地址"); printf("%-20s%-5d%-5s%-12s%-30s\n", c->data[ret].name, c->data[ret].age, c->data[ret].sex, c->data[ret].tele, c->data[ret].addr); } void ModifyContact(Contact* c) { char name[NAME_MAX]; assert(c); printf("请输入要修改人的姓名:"); scanf("%s", name); int ret = FindByName(c, name); if (ret == -1) { printf("要修改的人不存在!\n"); return; } //修改 printf("请输入姓名:"); scanf("%s", c->data[ret].name); printf("请输入年龄:"); scanf("%d", &c->data[ret].age); printf("请输入性别:"); scanf("%s", c->data[ret].sex); printf("请输入电话:"); scanf("%s", c->data[ret].tele); printf("请输入地址:"); scanf("%s", c->data[ret].addr); printf("修改成功!\n"); } int cmp(const void *a,const void *b) { return strcmp((*(PInfo*)a).age, (*(PInfo*)b).age); } void AgeSortContact(Contact* c) { assert(c); qsort(c->data, c->sz, sizeof(PInfo), cmp); printf("%-20s%-5s%-5s%-12s%-30s\n", "姓名", "年龄", "性别", "电话", "地址"); for (int i = 0; i sz; i++) { printf("%-20s%-5d%-5s%-12s%-30s\n", c->data[i].name, c->data[i].age, c->data[i].sex, c->data[i].tele, c->data[i].addr); } } test.c //文件用于测试通讯录的基本功能。 #define _CRT_SECURE_NO_WARNINGS 1 #include "contact.h"//自己定义的头文件用"" void menu() { printf("***********************************\n"); printf("********1.增加联系人***************\n"); printf(" \n"); printf("********2.删除指定联系人的信息*****\n"); printf(" \n"); printf("********3.查找指定联系人的信息*****\n"); printf(" \n"); printf("********4.修改指定联系人的信息*****\n"); printf(" \n"); printf("********5.排序通讯录的信息*********\n"); printf(" \n"); printf("********6.显示所有联系人的信息*****\n"); printf(" \n"); printf("********0.退出程序*****************\n"); printf("***********************************\n"); } enum Option { EXIT, ADD, DEL, SEARCH, MODIFY, SHOW, SORT }; int main() { int input = 0; Contact con; //初始化函数 InitContact(&con); do { menu(); printf("请输入你的选择:>"); scanf("%d", &input); switch (input) { case ADD: AddContact(&con); system("pause"); system("cls"); break; case DEL: DelContact(&con); system("pause"); system("cls"); break; case SEARCH: SearchContact(&con); system("pause"); system("cls"); break; case MODIFY: ModifyContact(&con); system("pause"); system("cls"); break; case SHOW: ShowContact(&con); system("pause"); system("cls"); break; case SORT: AgeSortContact(&con); system("pause"); system("cls"); break; case EXIT: DestroyContact(&con); printf("退出通讯录\n"); break; default: break; } } while (input); return 0; }
🎈7.柔性数组
C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做(柔性数组)成员。
✅注意点:
- 在结构体中
- 最后一个成员
- 未知大小的数组
//例如: typedef struct st_type { int i; int a[0];//柔性数组成员 }type_a; typedef struct st_type { int i; int a[];//柔性数组成员 }type_a; //两种写法是一样的
🔭7.1柔性数组的特点
- 结构中的柔性数组成员前面必须至少有一个其他成员。
- sizeof返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
typedef struct st_type { int i; int a[0];//柔性数组成员 }type_a; printf("%d\n", sizeof(type_a));//输出的是4
🔭7.2柔性数组的使用
#include #include struct S { int i; int a[];//柔性数组成员 }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S) + 20); if (ps == NULL) { perror("malloc"); return 1; } ps->i = 100; int i = 0; for (i = 0; i a[i] = i; } //打印 for (i = 0; i a[i]); } //释放 free(ps); ps = NULL; return 0; }
🔭7.3柔性数组的优势
//代码2 #include #include typedef struct st_type { int i; int* p_a; }type_a; int main() { type_a* p = (type_a*)malloc(sizeof(type_a)); p->i = 100; p->p_a = (int*)malloc(p->i * sizeof(int)); //业务处理 int i = 0; for (i = 0; i p_a[i] = i; } //释放空间 free(p->p_a); p->p_a = NULL; free(p); p = NULL; return 0; }
✅好处:
- 方便内存释放:如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
- 这样有利于访问速度.:连续的内存有益于提高访问速度,也有益于减少内存碎片。
好啦,关于动态内存管理的知识到这里就先结束啦,后期会继续更新学习C语言的相关知识,欢迎大家持续关注、点赞和评论!❤️❤️❤️
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。