进阶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.mzph.cn/news/682347.shtml

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

相关文章

PLC_博图系列☞FBFC

PLC_博图系列☞FB&FC 文章目录 PLC_博图系列☞FB&FC背景介绍FB&FC与C 类比博图中定义函数块 (FB)函数 (FC) 关键字&#xff1a; PLC、 西门子、 博图、 FC 、 FB 背景介绍 这是一篇关于PLC编程的文章&#xff0c;特别是关于西门子的博图软件。我并不是专业的P…

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

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

面试经典150题——串联所有单词的子串(困难)

"Opportunities dont happen, you create them." ​ - Chris Grosser 1. 题目描述 2. 题目分析与解析 2.1 思路一——暴力求解 遇见这种可能刚开始没什么思路的问题&#xff0c;先试着按照人的思维来求解该题目。对于一个人来讲&#xff0c;我想要找到 s 字符串中…

AJAXJSON入门篇

AJAX&JSON 概念&#xff1a;AJAX(Asynchronous JavaScript And XML):异步的JavaScript和XML AJAX作用&#xff1a; 与服务器进行数据交换&#xff1a;通过AJAX可以给服务器发送请求&#xff0c;并获取服务器响应的数据 使用了AJAX和服务器进行通信&#xff0c;就可以使用H…

基于servlet编写的表白墙项目(后端代码 含数据库操作)

前提准备 项目前端代码和效果 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"…

C语言——枚举类型

&#x1f4dd;前言&#xff1a; 在之前的文章中我们已经讲解了自定义类型中的结构体类型和联合体类型&#xff0c;现在我们再充分学习一下C语言中的枚举类型&#xff1a; 1&#xff0c;什么是枚举类型 2&#xff0c;枚举类型的定义和变量的声明 3&#xff0c;对变量进行赋值 &a…

455. Assign Cookies(分发饼干)

题目描述 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且每块饼干 j&#xff0c;都有一个…

OpenCompass 大模型评测

在浦语的大模型评测教程课程中&#xff0c;你可能需要完成的任务包括&#xff1a; 先修知识准备&#xff1a;这里你需要理解并掌握深度学习、NLP和PyTorch等相关知识。因为这些都是进行大模型评测的基础。模型理解&#xff1a;你需要对你要评测的模型有一个全面的理解&#xf…

FPGA实现ISP用于无人车、无人机配送的方案调研

查到一个always 奥唯思公司做的用FPGA实现ISP的方案&#xff0c;采用易灵思钛金16nm的FPGA Ti60F225&#xff0c;通过MIPI CSI RX采集图像传感器的数据&#xff0c;在FPGA内部经过一系列复杂的ISP运算后&#xff0c;再通过MIPI CSI TX将图像数据发送给后端。 一套完整的ISP&a…

【算法分析与设计】环形链表

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次…

实景剧本杀小程序:创新体验,沉浸式推理乐趣

随着科技的飞速发展&#xff0c;人们对于娱乐方式的追求也在不断升级。传统的桌面剧本杀游戏已经不能满足玩家的需求&#xff0c;他们渴望更加真实、刺激的游戏体验。正是这种需求推动下&#xff0c;实景剧本杀小程序应运而生&#xff0c;为玩家带来前所未有的推理乐趣。 实景…

【基础】第K大与第K小数

说明 给定一个长度为N(0< n< 10000)的序列&#xff0c;保证每一个序列中的数字a[i]是正整数 &#xff0c;编程要求求出整个序列中第k大的数字减去第k小的数字的值m&#xff0c;并判断m是否为质数。(0< k< n) 输入数据 第一行为2个数n&#xff0c;k&#xff08;…

ChatGPT高效提问—prompt常见用法(续篇八)

ChatGPT高效提问—prompt常见用法(续篇八) 1.1 对抗 ​ 对抗是一个重要主题,深入探讨了大型语言模型(LLM)的安全风险。它不仅反映了人们对LLM可能出现的风险和安全问题的理解,而且能够帮助我们识别这些潜在的风险,并通过切实可行的技术手段来规避。 ​ 截至目前,网络…

【Android】使用Android Studio打包APK文件

文章目录 1. 新建项目2. 打包生成APK3. 安装APK 1. 新建项目 打包APK之前&#xff0c;首先需要新建项目&#xff0c;有基础的可以跳过。 无基础的可以参考&#xff1a;使用Android Studio运行Hello World项目 2. 打包生成APK 1.找到Build -> Generate Signed Bundle or …

AMD FPGA设计优化宝典笔记(4)复位桥

高亚军老师的这本书《AMD FPGA设计优化宝典》&#xff0c;他主要讲了两个东西&#xff1a; 第一个东西是代码的良好风格&#xff1b; 第二个是设计收敛等的本质。 这个书的结构是一个总论&#xff0c;加上另外的9个优化&#xff0c;包含的有&#xff1a;时钟网络、组合逻辑、触…

机器视觉范例及深入

1.做一个魔法棒吧 获得了物体的坐标后&#xff0c;可以用它来完成一些有趣的事情&#xff0c;例如把物体当作“笔”在图像 上绘制出图样。我们可以选择一种颜色的黏土&#xff0c;将其固定在任意棒状物&#xff08;例如铅笔&#xff09;的一端 并揉成球形&#xff0c;做一个 …

函数求导法则【高数笔记】

【分类】 1. 四则运算求导 2. 复合运算求导 3. 整体思想求导 #整体思想求导本质是运用复合运算求导&#xff0c;只不过是对复合运算求导的一种精炼 #无论是具体函数还是抽象函数求导&#xff0c;方法是一致的 【四则运算求导】 加&#xff0c;减&#xff0c;乘&#xff0c;除&a…

Java基于微信小程序的电子竞技信息交流平台

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Promise与async await的作用及应用场景

在Web前端开发中&#xff0c;处理异步操作是非常常见的需求。为了解决这个问题&#xff0c;ES6引入了Promise和后续的async await。本文将介绍Promise和async await的作用&#xff0c;以及在实际开发中的应用场景。 一、Promise的作用及应用场景 Promise是一个表示异步操作最…