内存管理

进程空间

  • 程序,是经源码编译后的可执行文件,可执行文件可以多次被执行,比如我们可以多次打开 office。
  • 而进程,是程序加载到内存后开始执行,至执行结束,这样一段时间概念,多次打开的wps,每打开一次都是一个进程,当我们每关闭一个 office,则表示该进程结束。
  • 程序是静态概念,而进程动态/时间概念。
    ###进程空间图示
    有了进程和程序的概念以后,我们再来看一下,程序被加载到内存以后内存空间布局是什么样的

栈内存(Stack)

  • 栈中存放任意类型的变量,但必须是 auto 类型修饰的,即自动类型的局部变量, 随用随开,用完即消。
  • 内存的分配和销毁系统自动完成,不需要人工干预
  • 栈的最大尺寸固定,超出则引起栈溢出
    • 局部变量过多,过大 或 递归层数太多等就会导致栈溢出
int ages[10240*10240]; // 程序会崩溃, 栈溢出
#include <stdio.h>int main()
{// 存储在栈中, 内存地址从大到小int a = 10;int b = 20;printf("&a = %p\n", &a); // &a = 0060FEACprintf("&b = %p\n", &b); // &b = 0060FEA8return 0;
}

堆内存(Heap)

  • 堆内存可以存放任意类型的数据,但需要自己申请与释放
  • 堆大小,想像中的无穷大,但实际使用中,受限于实际内存的大小和内存是否连续性
int *p = (int *)malloc(10240 * 1024); // 不一定会崩溃
#include <stdio.h>
#include <stdlib.h>int main()
{// 存储在栈中, 内存地址从小到大int *p1 = malloc(4);*p1 = 10;int *p2 = malloc(4);*p2 = 20;printf("p1 = %p\n", p1); //  p1 = 00762F48printf("p2 = %p\n", p2); // p2 = 00762F58return 0;
}

malloc函数

函数声明 void * malloc(size_t _Size);
所在文件 stdlib.h
函数功能 申请堆内存空间并返回,所申请的空间并未初始化。
常见的初始化方法是 memset 字节初始化。
参数及返回解析
参数 size_t _size 表示要申请的字符数
返回值 void * 成功返回非空指针指向申请的空间 ,失败返回 NULL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{/** malloc* 第一个参数: 需要申请多少个字节空间* 返回值类型: void **/ int *p = (int *)malloc(sizeof(int));printf("p = %i\n", *p); // 保存垃圾数据/** 第一个参数: 需要初始化的内存地址* 第二个初始: 需要初始化的值* 第三个参数: 需要初始化对少个字节*/ memset(p, 0, sizeof(int)); // 对申请的内存空间进行初始化printf("p = %i\n", *p); // 初始化为0return 0;
}

free函数

  • 注意: 通过malloc申请的存储空间一定要释放, 所以malloc和free函数总是成对出现
函数声明 void free(void *p);
所在文件 stdlib.h
函数功能 释放申请的堆内存
参数及返回解析
参数 void* p 指向手动申请的空间
返回值 void 无返回
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{// 1.申请4个字节存储空间int *p = (int *)malloc(sizeof(int));// 2.初始化4个字节存储空间为0memset(p, 0, sizeof(int));// 3.释放申请的存储空间free(p);return 0;
}

calloc函数

函数声明 void *calloc(size_t nmemb, size_t size);
所在文件 stdlib.h
函数功能 申请堆内存空间并返回,所申请的空间,自动清零
参数及返回解析
参数 size_t nmemb 所需内存单元数量
参数 size_t size 内存单元字节数量
返回值 void * 成功返回非空指针指向申请的空间 ,失败返回 NULL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{/*// 1.申请3块4个字节存储空间int *p = (int *)malloc(sizeof(int) * 3);// 2.使用申请好的3块存储空间p[0] = 1;p[1] = 3;p[2] = 5;printf("p[0] = %i\n", p[0]);printf("p[1] = %i\n", p[1]);printf("p[2] = %i\n", p[2]);// 3.释放空间free(p);*/// 1.申请3块4个字节存储空间int *p = calloc(3, sizeof(int));// 2.使用申请好的3块存储空间p[0] = 1;p[1] = 3;p[2] = 5;printf("p[0] = %i\n", p[0]);printf("p[1] = %i\n", p[1]);printf("p[2] = %i\n", p[2]);// 3.释放空间free(p);return 0;
}

realloc函数

函数声明 void *realloc(void *ptr, size_t size);
所在文件 stdlib.h
函数功能 扩容(缩小)原有内存的大小。通常用于扩容,缩小会会导致内存缩去的部分数据丢失。
参数及返回解析
参数 void * ptr 表示待扩容(缩小)的指针, ptr 为之前用 malloc 或者 calloc 分配的内存地址。
参数 size_t size 表示扩容(缩小)后内存的大小。
返回值 void* 成功返回非空指针指向申请的空间 ,失败返回 NULL。
  • 注意点:

    • 若参数ptr==NULL,则该函数等同于 malloc
    • 返回的指针,可能与 ptr 的值相同,也有可能不同。若相同,则说明在原空间后面申请,否则,则可能后续空间不足,重新申请的新的连续空间,原数据拷贝到新空间, 原有空间自动释放
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{// 1.申请4个字节存储空间int *p = NULL;p = realloc(p, sizeof(int)); // 此时等同于malloc// 2.使用申请好的空间*p = 666;printf("*p = %i\n",  *p);// 3.释放空间free(p);return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{// 1.申请4个字节存储空间int *p = malloc(sizeof(int));printf("p = %p\n", p);// 如果能在传入存储空间地址后面扩容, 返回传入存储空间地址// 如果不能在传入存储空间地址后面扩容, 返回一个新的存储空间地址p = realloc(p, sizeof(int) * 2);printf("p = %p\n", p);// 2.使用申请好的空间*p = 666;printf("*p = %i\n",  *p);// 3.释放空间free(p);return 0;
}

链表

  • 链表实现了,内存零碎数据的有效组织。比如,当我们用 malloc 来进行内存申请的时候,当内存足够,但是由于碎片太多,没有连续内存时,只能以申请失败而告终,而用链表这种数据结构来组织数据,就可以解决上类问题。

    ##静态链表
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 1.定义链表节点
typedef struct node{int data;struct node *next;
}Node;
int main()
{// 2.创建链表节点Node a;Node b;Node c;// 3.初始化节点数据a.data = 1;b.data = 3;c.data = 5;// 4.链接节点a.next = &b;b.next = &c;c.next = NULL;// 5.创建链表头Node *head = &a;// 6.使用链表while(head != NULL){int currentData = head->data;printf("currentData = %i\n", currentData);head = head->next;}return 0;
}

动态链表

  • 静态链表的意义不是很大,主要原因,数据存储在栈上,栈的存储空间有限,不能动态分配。所以链表要实现存储的自由,要动态的申请堆里的空间。

  • 有一个点要说清楚,我们的实现的链表是带头节点。至于,为什么带头节点,需等大家对链表有个整体的的认知以后,再来体会,会更有意义。

  • 空链表

    • 头指针带了一个空链表节点, 空链表节点中的next指向NULL
#include <stdio.h>
#include <stdlib.h>// 1.定义链表节点
typedef struct node{int data;struct node *next;
}Node;
int main()
{Node *head = createList();return 0;
}
// 创建空链表
Node *createList(){// 1.创建一个节点Node *node = (Node *)malloc(sizeof(Node));if(node == NULL){exit(-1);}// 2.设置下一个节点为NULLnode->next = NULL;// 3.返回创建好的节点return node;
}
  • 非空链表

    • 头指针带了一个非空节点, 最后一个节点中的next指向NULL

动态链表头插法

  • 1.让新节点的下一个节点等于头结点的下一个节点
  • 2.让头节点的下一个节点等于新节点
#include <stdio.h>
#include <stdlib.h>// 1.定义链表节点
typedef struct node{int data;struct node *next;
}Node;
Node *createList();
void printNodeList(Node *node);
int main()
{Node *head = createList();printNodeList(head);return 0;
}
/*** @brief createList 创建链表* @return  创建好的链表*/
Node *createList(){// 1.创建头节点Node *head = (Node *)malloc(sizeof(Node));if(head == NULL){return NULL;}head->next = NULL;// 2.接收用户输入数据int num = -1;printf("请输入节点数据\n");scanf("%i", &num);// 3.通过循环创建其它节点while(num != -1){// 3.1创建一个新的节点Node *cur = (Node *)malloc(sizeof(Node));cur->data = num;// 3.2让新节点的下一个节点指向头节点的下一个节点cur->next = head->next;// 3.3让头节点的下一个节点指向新节点head->next = cur;// 3.4再次接收用户输入数据scanf("%i", &num);}// 3.返回创建好的节点return head;
}
/*** @brief printNodeList 遍历链表* @param node 链表指针头*/
void printNodeList(Node *node){Node *head = node->next;while(head != NULL){int currentData = head->data;printf("currentData = %i\n", currentData);head = head->next;}
}

动态链表尾插法

  • 1.定义变量记录新节点的上一个节点
  • 2.将新节点添加到上一个节点后面
  • 3.让新节点成为下一个节点的上一个节点
#include <stdio.h>
#include <stdlib.h>// 1.定义链表节点
typedef struct node{int data;struct node *next;
}Node;
Node *createList();
void printNodeList(Node *node);
int main()
{Node *head = createList();printNodeList(head);return 0;
}
/*** @brief createList 创建链表* @return  创建好的链表*/
Node *createList(){// 1.创建头节点Node *head = (Node *)malloc(sizeof(Node));if(head == NULL){return NULL;}head->next = NULL;// 2.接收用户输入数据int num = -1;printf("请输入节点数据\n");scanf("%i", &num);// 3.通过循环创建其它节点// 定义变量记录上一个节点Node *pre = head;while(num != -1){// 3.1创建一个新的节点Node *cur = (Node *)malloc(sizeof(Node));cur->data = num;// 3.2让新节点链接到上一个节点后面pre->next = cur;// 3.3当前节点下一个节点等于NULLcur->next = NULL;// 3.4让当前节点编程下一个节点的上一个节点pre = cur;// 3.5再次接收用户输入数据scanf("%i", &num);}// 3.返回创建好的节点return head;
}
/*** @brief printNodeList 遍历链表* @param node 链表指针头*/
void printNodeList(Node *node){Node *head = node->next;while(head != NULL){int currentData = head->data;printf("currentData = %i\n", currentData);head = head->next;}
}

动态链优化

#include <stdio.h>
#include <stdlib.h>// 1.定义链表节点
typedef struct node{int data;struct node *next;
}Node;
Node *createList();
void printNodeList(Node *node);
void insertNode1(Node *head, int data);
void insertNode2(Node *head, int data);
int main()
{// 1.创建一个空链表Node *head = createList();// 2.往空链表中插入数据insertNode1(head, 1);insertNode1(head, 3);insertNode1(head, 5);printNodeList(head);return 0;
}
/*** @brief createList 创建空链表* @return  创建好的空链表*/
Node *createList(){// 1.创建头节点Node *head = (Node *)malloc(sizeof(Node));if(head == NULL){return NULL;}head->next = NULL;// 3.返回创建好的节点return head;
}
/*** @brief insertNode1 尾插法插入节点* @param head 需要插入的头指针* @param data 需要插入的数据* @return  插入之后的链表*/
void insertNode1(Node *head, int data){// 1.定义变量记录最后一个节点Node *pre = head;while(pre != NULL && pre->next != NULL){pre = pre->next;}// 2.创建一个新的节点Node *cur = (Node *)malloc(sizeof(Node));cur->data = data;// 3.让新节点链接到上一个节点后面pre->next = cur;// 4.当前节点下一个节点等于NULLcur->next = NULL;// 5.让当前节点编程下一个节点的上一个节点pre = cur;
}
/*** @brief insertNode1 头插法插入节点* @param head 需要插入的头指针* @param data 需要插入的数据* @return  插入之后的链表*/
void insertNode2(Node *head, int data){// 1.创建一个新的节点Node *cur = (Node *)malloc(sizeof(Node));cur->data = data;// 2.让新节点的下一个节点指向头节点的下一个节点cur->next = head->next;// 3.让头节点的下一个节点指向新节点head->next = cur;
}
/*** @brief printNodeList 遍历链表* @param node 链表指针头*/
void printNodeList(Node *node){Node *head = node->next;while(head != NULL){int currentData = head->data;printf("currentData = %i\n", currentData);head = head->next;}
}

链表销毁

/*** @brief destroyList 销毁链表* @param head 链表头指针*/
void destroyList(Node *head){Node *cur = NULL;while(head != NULL){cur = head->next;free(head);head = cur;}
}

链表长度计算

/*** @brief listLength 计算链表长度* @param head 链表头指针* @return 链表长度*/
int listLength(Node *head){int count = 0;head = head->next;while(head){count++;head = head->next;}return count;
}

链表查找

/*** @brief searchList 查找指定节点* @param head 链表头指针* @param key 需要查找的值* @return*/
Node *searchList(Node *head, int key){head = head->next;while(head){if(head->data == key){break;}else{head = head->next;}}return head;
}

链表删除

void deleteNodeList(Node *head, Node *find){while(head->next != find){head = head->next;}head->next = find->next;free(find);
}

作业

  • 给链表排序
/*** @brief bubbleSort 对链表进行排序* @param head 链表头指针*/
void bubbleSort(Node *head){// 1.计算链表长度int len = listLength(head);// 2.定义变量记录前后节点Node *cur = NULL;// 3.相邻元素进行比较, 进行冒泡排序for(int i = 0; i < len - 1; i++){cur = head->next;for(int j = 0; j < len - 1 - i; j++){printf("%i, %i\n", cur->data, cur->next->data);if((cur->data) > (cur->next->data)){int temp = cur->data;cur->data = cur->next->data;cur->next->data = temp;}cur = cur->next;}}
}
/*** @brief sortList 对链表进行排序* @param head 链表头指针*/
void sortList(Node *head){// 0.计算链表长度int len = listLength(head);// 1.定义变量保存前后两个节点Node *sh, *pre, *cur;for(int i = 0; i < len - 1; i ++){sh = head; // 头节点pre = sh->next; // 第一个节点cur = pre->next; // 第二个节点for(int j = 0; j < len - 1 - i; j++){if(pre->data > cur->data){// 交换节点位置sh->next = cur;pre->next = cur->next;cur->next = pre;// 恢复节点名称Node *temp = pre;pre = cur;cur = temp;}// 让所有节点往后移动sh = sh->next;pre = pre->next;cur = cur->next;}}
}
  • 链表反转
/*** @brief reverseList 反转链表* @param head 链表头指针*/
void reverseList(Node *head){// 1.将链表一分为二Node *pre, *cur;pre = head->next;head->next = NULL;// 2.重新插入节点while(pre){cur = pre->next;pre->next = head->next;head->next = pre;pre = cur;}
}

如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是江哥持续更新的动力。

江哥带你玩转C语言 - 16-内存管理和链表相关推荐

  1. 江哥带你玩转C语言 02- 创建第一个C语言程序

    Hello world 这个世界上, 几乎所有程序员入门的第一段代码都是Hello World. 原因是当年C语言的作者Dennis Ritchie(丹尼斯 里奇)在他的名著中第一次引入, 传为后世经 ...

  2. 江哥带你玩转C语言| 12 -二维数组和字符串

    二维数组 所谓二维数组就是一个一维数组的每个元素又被声明为一 维数组,从而构成二维数组. 可以说二维数组是特殊的一维数组. 示例: int a[2][3] = { {80,75,92}, {61,65 ...

  3. 江哥带你玩转C语言 | 11- C语言排序算法

    计数排序(Counting Sort) 计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出.它的优势在于在对一定范围内的整数排序时,快于任何比较排序算法. ...

  4. 江哥带你玩转C语言 | 04-C语言常量和变量

    什么是数据? 生活中无时无刻都在跟数据打交道 例如:人的体重.身高.收入.性别等数据等 在我们使用计算机的过程中,也会接触到各种各样的数据 例如: 文档数据.图片数据.视频数据等 数据分类 静态的数据 ...

  5. 江哥带你玩转C语言 | 03-C语言关键字和标识符

    什么是注释? 注释是在所有计算机语言中都非常重要的一个概念,从字面上看,就是注解.解释的意思 注释可以用来解释某一段程序或者某一行代码是什么意思,方便程序员之间的交流沟通 注释可以是任何文字,也就是说 ...

  6. 江哥带你玩转C语言 | 17-文件操作

    文件基本概念 文件流: C 语言把文件看作是一个字符的序列,即文件是由一个一个字符组成的字符流,因此 c 语言将文件也称之为文件流. 文件分类 文本文件 以 ASCII 码格式存放,一个字节存放一个字 ...

  7. 江哥带你玩转C语言 | 09 - C语言进制和位运算

    进制基本概念 什么是进制? 进制是一种计数的方式,数值的表示形式 常见的进制 十进制.二进制.八进制.十六进制 进制书写的格式和规律 十进制 0.1.2.3.4.5.6.7.8.9 逢十进一 二进制 ...

  8. 江哥带你玩转C语言 | 13- 一级指针和多级指针

    指针基本概念 什么是地址 生活中的地址: 内存地址: 地址与内存单元中的数据是两个完全不同的概念 地址如同房间编号, 根据这个编号我们可以找到对应的房间 内存单元如同房间, 房间是专门用于存储数据的 ...

  9. 江哥带你玩转C语言 | 10- C语言数组

    数组的基本概念 数组,从字面上看,就是一组数据的意思,没错,数组就是用来存储一组数据的 在C语言中,数组属于构造数据类型 数组的几个名词 数组:一组相同数据类型数据的有序的集合 数组元素: 构成数组的 ...

最新文章

  1. LeetCode刷题记录7——824. Goat Latin(easy)
  2. office excel单列数据类型不一致,导入时部分数据为空
  3. mogodb 设置用户名密码认证
  4. java 转换url中文参数
  5. EPS 转 pdf 在线
  6. oracle sequences优化_性能优化-Oracle RAC中的Sequence Cache问题
  7. Docker、Kubernetes与PaaS不得不说的渊源
  8. Delphi运行期错误
  9. 快手视频以及评论获取
  10. ESP32-cam 初体验 从esp32-cam的购买到局域网监控的实现
  11. AWS环境搭建(六):Linux上部署wowza,并配置ssl证书
  12. 大话USB驱动之基础概念
  13. android 代码设置 键盘适应_实现Android键盘的中英文适配
  14. android viewpager中每个view,ViewPager系列之 打造一个通用的ViewPager
  15. IT杂谈(一):炫酷好玩网站汇总
  16. HTML5期末考核大作业:华为官网 ( 2页带轮播图)
  17. October CMS
  18. Gartner 公布 2022 新兴技术成熟度曲线,这些技术趋势最值得关注
  19. 爬虫练习:爬取网易云音乐热歌榜全部歌曲的热门评论
  20. 大数据可视化python_大数据分析之Python数据可视化的四种简易方法

热门文章

  1. 独家首发“JVM超硬核笔录”,连阿里面试官都赞不绝口(超清PDF+Xmind思维导图)
  2. 什么是数字源表?主要应用在哪些方面?
  3. 福禄克FLUKE CFP2-100-Q在测试光纤中,该选OM3还是OM4光纤标准?
  4. 青龙面板用wxpusher随时监控JD收益(之青龙2.12版)
  5. 【泛微ecology】Linux下 ecology日志截取
  6. [live555]rtsp直播基于live555的实现
  7. 网上拍卖系统/拍卖网站
  8. Idea自动补全热键被win10系统占用
  9. 在下列集成电路说法中不正确_关于集成电路(IC),下列说法中,正确的是________。...
  10. ****网 购书投诉电话 生活启示