进阶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.使用动态内存相关的知识改进通讯录

🎈1.为什么存在动态内存分配

✅截止目前,我们掌握的内存开辟的方式有:

    int a = 10;//在栈空间上开辟4个字节char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的方式有两个特点:

  1. 空间开辟的大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需的内存在编译时分配。

🔎但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道那数组的编译时开辟空间的方式就不能满足了。这个时候,我们就只能试试动态存开辟!

🎈2.动态内存函数的介绍

🔭2.1malloc和free函数

🏆C语言提供了一个动态开辟内存的函数:

void *malloc(size_t size);

✅这个函数向内存申请一块连续可用的空间,并返回这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者来决定。
  • 如果参数size0malloc的行为是标准是未定义的,取决于编译器。

在这里插入图片描述

int main()
{//申请一块空间,用来存放10个整型int* p = (int*)malloc(10 * sizeof(int));return 0;
}

✅内存的存储:
在这里插入图片描述

📖注意: mallocfree都声明在stdlib.h的头文件中。

✅运行示例:

#include <stdio.h>
#include <stdlib.h>
int main()
{//申请一块空间,用来存放10个整型int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//使用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d ", p[i]);}return 0;
}

在这里插入图片描述
🏆C语言提供了另外一个函数free,专门用来做动态内存的释放和回收:

void free(void *ptr);

free函数用来释放动态开辟的内存。
在这里插入图片描述

  • 如果参数ptr指向的空间不是动态开辟的,则free函数的行为是未定义的。
  • 如果参数ptrNULL指针,则函数什么事都不做。

🌞malloc函数申请的空间,是怎么释放的呢?

  1. free释放-主动释放
  2. 程序退出后,malloc申请的空间,也会被操作系统回收。-被动回收
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;

🔭2.2calloc函数

在这里插入图片描述
🔎malloccalloc函数除了参数的区别,calloc函数申请好空间后,会将空间初始化0,但是malloc函数不会初始化。

🔭2.3realloc函数

在这里插入图片描述
✅该函数用于对我们已开辟动态空间大小的调整!

#include <stdio.h>
#include <stdlib.h>
int main()
{//申请一块空间,用来存放10个整型int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//使用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}//打印for (i = 0; i < 10; i++){printf("%d ", p[i]);}//空间不够,希望调整空间为20个整型的空间realloc(p, 20 * sizeof(int));//释放free(p);p = NULL;return 0;
}

🔎但是这种方法是有问题的,因为realloc开辟空间也可能会失败,失败的时候返回NULL!因此,如果这个时候,我们还用p来接收的话,那么我们原来有的10个字节的空间可能也丢失了。

//更改:
//空间不够,希望调整空间为20个整型的空间int* ptr = (int*)realloc(p, 20 * sizeof(int));if (ptr != NULL){p = ptr;}

realloc函数是如何工作的呢?

  1. 要扩展的内存就直接在原有内存之后追加空间,原来空间的数据不发生变化。
  2. 原有空间之后没有足够多的空间,扩展的方法就是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存空间。

🎈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 <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);
}

🔭3.3对非动态开辟空间内存使用free释放

void test()
{int a = 10;int* p = &a;free(p);//no
}

🔭3.4使用free释放一块动态开辟内存的一部分

void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}

🔭3.5对同一块动态内存多次释放

void test()
{int* p = (int*)malloc(100);free(p);free(p);//重复释放
}

🔭3.6动态开辟内存忘记释放(内存泄漏)

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();while (1);
}

❗**注:**动态开辟的空间一定要释放,并且正确释放。

🎈4.几个经典的笔试题

🔭4.1题目一

void GetMemory(char* p)
{p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}

🏆请问运行Test函数会有什么样的结果?

//当程序对NULL的进行解引用操作的时候,程序崩溃!后序代码也不会执行。
strcpy(str, "hello world");//同时malloc开辟的空间没有释放,内存会泄露!
p = (char*)malloc(100);
//更正:
#include <stdio.h>
#include <stdlib.h>
void GetMemory(char** p)
{*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;
}

✅运行:
在这里插入图片描述

//更正二:
#include <stdio.h>
#include <stdlib.h>
char* GetMemory()
{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;
}

在这里插入图片描述

🔭4.2题目二

#include <stdio.h>
#include <stdlib.h>
char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}

🏆请问运行Test函数会有什么样的结果?

str = GetMemory();//返回栈空间地址的问题,野指针

✅可以这样进行修改:

🔭4.3题目三

#include <stdio.h>
#include <stdlib.h>
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
int main()
{Test();return 0;
}

🏆请问运行Test函数会有什么样的结果?

*p = (char*)malloc(num);//malloc申请的空间没有释放
//更正:
#include <stdio.h>
#include <stdlib.h>
void GetMemory(char** p, int num)
{*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;
}

在这里插入图片描述

🔭4.4题目四

#include <stdio.h>
#include <stdlib.h>
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}

🏆请问运行Test函数会有什么样的结果?

strcpy(str, "world");//非法访问内存的情况,str为野指针		

🎈5.C/C++程序的内存开辟

在这里插入图片描述
C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
  3. 数据段(静态区):(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

🎈6.使用动态内存相关的知识改进通讯录

  1. 通讯录刚开始时,可以存放3个人的信息。
  2. 空间如果放满,每次可以增加2个信息的空间。
contact.h
#pragma once
//类型的声明
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#define Max 100
#define NAME_MAX 10
#define DEFAULT_SZ 3
typedef struct PepInfo
{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 < c->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 < c->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 < c->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 < c->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;
}

好啦,关于动态内存管理的知识到这里就先结束啦,后期会继续更新学习C语言的相关知识,欢迎大家持续关注、点赞和评论!❤️❤️❤️

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/468784.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

车载软件架构 —— Adaptive AUTOSAR软件架构

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师&#xff08;Wechat&#xff1a;gongkenan2013&#xff09;。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 本就是小人物&#xff0c;输了就是输了&#…

安卓价值1-如何在电脑上运行ADB

ADB&#xff08;Android Debug Bridge&#xff09;是Android平台的调试工具&#xff0c;它是一个命令行工具&#xff0c;用于与连接到计算机的Android设备进行通信和控制。ADB提供了一系列命令&#xff0c;允许开发人员执行各种操作&#xff0c;包括但不限于&#xff1a; 1. 安…

C++中的析构函数

一、析构函数概念 析构函数不是完成对象的销毁&#xff0c;对象的销毁是由编译器完成的。析构函数完成的是对象中资源的清理工作。通常是对对象中动态开辟的空间进行清理。 二、析构函数特性 1.析构函数的函数名是 ~类名 2.析构函数无参数无返回值 3.一个类中只能有一个析…

Unity(单元测试)在STM32上的移植与应用

概述 Unity Test是一个为C构建的单元测试框架。本文基于STM32F407为基础&#xff0c;完全使用STM32CubeIDE进行开发&#xff0c;移植和简单使用Unity。 单片机型号&#xff1a;STM32F407VET6 软件&#xff1a;STM32CubeIDE Version: 1.14.1 Unity Version&#xff1a;2.…

新项目,从0到1,SpringBoot+Vue.js权限管理系统,拿去做毕设

大家好&#xff0c;我是 jonssonyan 最近把以前做的权限管理系统重新整理了一下&#xff08;将一些不规范的地方规范了一下&#xff0c;并且在关键地方写了注释&#xff09;&#xff0c;代码全部开源&#xff0c;这个项目是以现在主流的前后端分离模式开发的&#xff0c;包含前…

Django学习全纪录:编写你的第一个 Django 应用,Django内置数据库的配置,以及扩展性的数据库介绍和配置

天下古今之庸人&#xff0c;皆以一惰字致败&#xff1b;天下古今之人才&#xff0c;皆以一傲字致败。——[清]曾国藩 导言 大家好&#xff0c;在上一篇文章里&#xff0c;我们一起学习了Django的视图以及路由&#xff0c;并且对Django的应用有了初步的认识&#xff0c;掌握了…

vim编辑代码后退出编辑显示vim编辑的内容

在/etc/profile.d/下新建terminal.sh&#xff1a; 在terminal.sh里添加如下代码&#xff1a; #!/bin/bashexport TERMlinux 然后同步文件到内存&#xff1a; source /etc/profile

第二十九回 施恩三入死囚牢 武松大闹飞云浦-分布式版本控制系统Git使用

武松要蒋门神答应三件事&#xff1a;离开快活林、东西都归还施恩&#xff0c;公开对施恩赔礼道歉&#xff0c;不许在孟州住。蒋门神不得已都答应了&#xff0c;灰溜溜地离开了孟州城。 一个月之后&#xff0c;天气转凉&#xff0c;张都监调武松到孟州城&#xff0c;做了他的亲…

JDK Development Kit 21.0.2的安装和使用

下载链接&#xff1a;https://www.oracle.com/java/technologies/downloads/#jdk21-windows&#xff0c;选择Windows-x64 Installer&#xff0c;下载下来的是一个exe程序&#xff0c;双击进行安装。 安装时可以修改安装位置&#xff0c;其余选项都不用修改。我的安装位置选择默…

猫头虎分享已解决Bug || Invariant Violation in React: Element Type is Invalid ‍

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

中小学信息学奥赛CSP-J认证 CCF非专业级别软件能力认证-入门组初赛模拟题第二套(选择题)

CSP-J入门组初赛模拟题二 1、在计算机内部用来传送、存贮、加工处理的数册或指令都是以()形式进行的 A、二进制 B、八进制 C、十进制 D、智能拼音 答案&#xff1a;A 考点分析&#xff1a;主要考查小朋友们计算机相关知识&#xff0c;在计算机中都是采用二进制运算&#…

猫头虎分享已解决Bug || TypeError: can‘t pickle _thread.lock objects

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …