《C和指针》读书笔记(第十一章 动态内存分配)

news2024/11/25 12:58:38

目录

  • 0 简介
  • 1 为什么使用动态内存分配
  • 2 malloc和free
  • 3 calloc和realloc
  • 4 使用动态分配的内存
  • 5 常见的动态内存错误
  • 6 内存分配实例
    • 6.1 排序一列整型值
    • 6.2 复制字符串
    • 6.3 变体记录的创建与销毁
  • 7 总结

0 简介

在实际开发中(C语言),数组的元素存储于内存中连续的位置上。但是用数组存储数据有个弊端,就是在程序运行之前我们就要知道其大小,在实际开发中,我们并不总能对需要申请的内存做到精准把握,若不采取其他手段,会让开发人员焦头烂额。

作为C语言的老大哥,C++显然可以高枕无忧,在面临很多复杂的场景时,往往可以采用容器从容应对,游刃有余。具体可参考链接:C++常见容器一网打尽

这样的问题,C语言也有自己的应对措施,为了打破这样的僵局,今天的主角轻施脂粉,深情款款地向我们走来,这就是动态内存分配(C++中同样适用)。

本篇内容概览:在这里插入图片描述


1 为什么使用动态内存分配

如上所述,很多时候,我们并不知道我们需要申请多大的内存来存放数据,太大浪费空间,太小不够用。

2 malloc和free

malloc和free是一对亲兄弟,前者负责申请内存,后者 负责释放内存。分工明确,简单高效。这两个函数的原型如下:

void *malloc(size_t  size);
void free(void *pointer);

malloc分配的就是一块连续的内存。同时,实际分配的内存可能比我们申请的稍微多一点,具体的大小取决于编译器

如果内存池是空的,或者可用内存无法满足请求,malloc函数向操作系统请求,要求得到更多的内存,并在这块新内存上执行分配任务。如果操作系统无法向malloc提供更多的内存,就会返回一个NULL指针。

free的参数必须要么是NULL,要么是一个先前从malloccallocrealloc返回的值。向free传递一个NULL不会产生任何效果。

具体的案例会在后续内容中提及。

3 calloc和realloc

另外还有两个内存分配函数callocrealloc。它们的原型如下所示:

void *calloc(size_t num_elements, size_t element_size);
void *realloc(void *ptr, size_t new_size);

callocmalloc有两个区别:

  • 从形式上看,malloc传入的是总字节数,而calloc传入的是元素数和每个元素所占的字节数。
  • 从作用上看,malloc只负责申请内存空间,而calloc不仅仅申请了内存空间,还将其初始化为0

从名称上也可以看出来: calloc = clear + malloc,意为清零and申请内存


realloc则是修改/重新申请一块内存。

  1. 如果p指向的空间之后足够的空间可以追加,则直接追加,返回的是p原来的起始地址。
  2. 如果p指向的空间之后没有足够的空间可以追加,则realloc函数会重新找一个新的内存区域,重新开辟一块new_size个字节的动态内存空间,并且把原来内存空间的数据拷贝回来,释放旧的内存空间还给操作系统,最后返回新开辟的内存空间的起始地址

第一种情况如下图所示:
在这里插入图片描述
第二种情况如下图所示:
在这里插入图片描述

从名称上也可以看出来: realloc = re + malloc,意为重新申请内存

4 使用动态分配的内存

书中有个例子,如下:

	int  *pi;
	pi = malloc(100);
	if (pi == NULL)
	{
		printf("Out of memory!\n");
		exit(1);
	}

这个例子很好懂,我们分配一个100字节的内存,如果分配失败了,就打印输出错误,并退出当前正在执行的程序。
当然,我们也可以自己写一个简单的程序,如下:

#include<stdio.h>
#include<stdlib.h>
//定义返回值类型
typedef enum res
{
	FASLE,
	TRUE
}res_bool;
//分配内存并初始化、打印输出
res_bool fun_malloc(int const size)
{
	int *p;
	p = malloc(sizeof(int) * 25);
	if(p == NULL)
		return FASLE;
	else
	{
		for (int i = 0; i < size; i++)
			p[i] = i;
	}
	for (int i = 0; i < size; i++)
		printf("%d\t", p[i]);
	free(p);
	p = NULL;
	return TRUE;
}
int main()
{
	if (fun_malloc(25) == TRUE)
	{
		printf("内存分配成功!");
	}
	else
	{
		printf("内存分配失败!");
	}
	system("pause");
	return 0;
}

这就是一个比较完整的案例,分配内存,初始化,并验证内存分配是否成功。

5 常见的动态内存错误

常见的动态内存错误有两种:

  1. 一种是根本没有判断内存是否申请成功,就直接使用,这样可能会出现意想不到的问题。
  2. 一种是操作时超出了分配内存的边界,同样也可能会出现意想不到的问题。

内存错误不好写具体的案例,只需平时编程注意即可。

6 内存分配实例

6.1 排序一列整型值

排序算法是工程开发中最常见,最经典的算法,常见的排序算法有十种,感兴趣的请移步:
十大经典排序算法(C语言实现)
下面给的例子是书中给的,用的是库函数qsort进行排序,据说底层采用的是快速排序算法。

#include <stdlib.h>
#include <stdio.h>
//该函数由qsort调用,用于比较整型值
int compare_integers(void const *a, void const *b)
{
	register int const *pa = a;
	register int const *pb = b;
	return *pa > *pb ? 1 : *pa < *pb ? -1 : 0;
}
//主函数
int main()
{
	int *array;
	int n_values;
	int i;
	//观察共有多少个值
	printf("How many values are there?");
	if (scanf_s("%d", &n_values) != 1 || n_values <= 0)
	{
		printf("Illegal number of values.\n");
		exit(EXIT_FAILURE);
	}
	//分配内存,用于存储这些值
	array = malloc(n_values * sizeof(int));
	if (array == NULL)
	{
		printf("Can't get memory for that many values.\n");
		exit(EXIT_FAILURE);
	}
	//读取这些值
	for (i = 0; i < n_values; i += 1)
	{
		printf("?");
		if (scanf_s("%d", array + i) != 1)
		{
			printf("error.\n");
			free(array);
			exit(EXIT_FAILURE);
		}
	}
	//对这些值排序
	qsort(array, n_values, sizeof(int), compare_integers);
	//打印这些值
	for (i = 0; i < n_values; i += 1)
		printf("%d\n",array[i]);
	//释放内存并推出
	free(array);
	system("pause");
	return EXIT_SUCCESS;
}

运行,打印输出:
在这里插入图片描述
基本上没有什么难点,唯一的难点是compare_integers函数的返回值用了嵌套的条件表达式,条件表达式就是简化版的条件语句(并非所有情况下都可以“简化”),稍微有点绕,关于条件表达式,可以参考《C和指针》读书笔记(第五章 操作符和表达式)的2.1.8小节。

6.2 复制字符串

复制字符串也有现成的库函数可以用,书上的例子仅仅是给新的字符串开辟了空间,仅此而已(略有改动)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *my_strdup(char const *string)
{
	char *new_string;
	new_string = (char *)malloc(strlen(string) + 1);
	if (new_string != NULL)
		strcpy(new_string, string);
	return new_string;
}
int main()
{
	char *new_p;
	char base_char[] = "Hello World!";
	//复制字符串
	new_p = my_strdup(base_char);
	//检查是否顺利复制
	if (new_p == NULL)
	{
			printf("error.\n");
			free(new_p);
			exit(EXIT_FAILURE);
	}
	//检查复制结果
	for (int i = 0; i < (int)(strlen(base_char)); i++)
	{
		if (new_p[i] != base_char[i])
		{
			printf("new_p[%d] != base_char[%d]", i, i);
			free(new_p);
			exit(EXIT_FAILURE);
		}
			
	}
	printf("success.\n");
	free(new_p);
	return  0;
}

运行,打印输出:
在这里插入图片描述
可以看到,字符串复制成功。从这个例子也可以看出动态内存分配在开发中的方便之处。

6.3 变体记录的创建与销毁

最后一个例子说明了可以怎样使用动态内存分配来消除使用变体记录造成的内存空间浪费。程序中用到了结构体和联合体的知识,想了解相关知识,请移步:《C和指针》读书笔记(第十章 结构和联合)

先创建一个头文件,定义需要用到的结构体

#pragma once
//包含零件专用信息的结构
typedef struct {
	int cost;
	int supplier;
}Partinfo;
//存储配件专用信息的结构
typedef struct {
	int n_parts;
	struct SUBASSYPART{
		char partno[10];
		short quan;
	} *part;
}Subassyinfo;
//存货记录结构,一个变体记录
typedef struct {
	char partno[10];
	int quan;
	enum {PART, SUBASSY} type;
	union {
		Partinfo *part;
		Subassyinfo *subassy;
	}info;
}Invrec;

再写创建变体记录的相关程序:

#include <stdio.h>
#include <stdlib.h>
#include "inventor.h"
Invrec *creat_subassy_record(int n_parts)
{
	Invrec *new_rec;
	//试图为Inverc部分分配内存
	new_rec = malloc(sizeof(Invrec));
	if (new_rec != NULL)
	{
		//内存分配成功,现在存储SUBASSYPART部分
		new_rec->info.subassy = malloc(sizeof(Subassyinfo));
		if (new_rec->info.subassy != NULL)
		{
			//为零件获取一个足够大的数组
			new_rec->info.subassy->part = malloc(n_parts * sizeof(struct SUBASSYPART));
			if (new_rec->info.subassy->part != NULL) 
			{
				//获取内存,填充我们已知道的字段,然后返回
				new_rec->type = SUBASSY;
				new_rec->info.subassy->n_parts = n_parts;
				return new_rec;
			}
			//内存已用完,释放我们原先分配的内存
			free(new_rec->info.subassy);
		}
		free(new_rec);
	}
	return NULL;
}

还有变体记录销毁的相关程序:

#include <stdlib.h>
#include "inventor.h"
void discard_inventory_record(Invrec *record)
{
	//删除记录中的变体部分
	switch (record->type)
	{
	case SUBASSY:
		free(record->info.subassy->part);
		free(record->info.subassy);
		break;
	case PART:
		free(record->info.part);
		break;
	}
	//删除记录的主体部分
	free(record);
}

这个例子比较复杂,其中有结构体的嵌套,这就关系到了内存的层层申请,然后再层层释放。

7 总结

本章内容不是很多,但却非常实用。当数组被声明时,必须在编译时知道它的长度。动态内存分配允许程序为一个长度在运行时才知道的数组分配内存空间。

---END---

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

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

相关文章

JDK,JRE,JVM有什么区别?跨平台?跨语言?

JDK Java Development Kit&#xff08;Java开发工具包&#xff09;&#xff0c;提供了Java的开发环境和运行环境。包含Java源文件的编译器Javac&#xff0c;还有调试和分析工具。 JRE Java Runtime Environment&#xff08;Java运行环境&#xff09;包含了Java虚拟机&#xff…

WPF开发txt阅读器10:语音播报快进快退

文章目录 MySpeech类快进 文章目录 MySpeech类快进 txt阅读器系列&#xff1a; 需求分析和文件读写目录提取类&#x1f48e;列表控件与目录字体控件绑定&#x1f48e;前景/背景颜色书籍管理系统&#x1f48e;用树形图管理书籍语音播放&#x1f48e;播放进度显示 MySpeech类 …

MySQL 中有哪些锁?

数据库中锁的设计初衷处理并发问题&#xff0c;作为多用户共享资源&#xff0c;当出现并发访问的时候&#xff0c;数据库需要合理控制资源访问规则。锁就是实现这些访问规则中的重要数据。 锁的分类 根据加锁范围&#xff0c;MySQL 里面的锁可以分成全局锁、表级锁、行锁三类…

计算机视觉算法——BEV Perception算法总结

计算机视觉算法——BEV Perception算法总结 计算机视觉算法——BEV Perception算法总结1. Homograph Based——3D LaneNet2. Depth Based——LSS3. MLP Based——PON4. Transformer Based——BEVFormer5. Transformer Based——Translating Image into Maps 计算机视觉算法——…

急于生成人工智能是有风险的:如何保护数据

每天的业务用户都在尝试使用 ChatGPT 和其他生成式 AI 工具。事实上&#xff0c; Gartner 预测&#xff0c; 到 2025 年&#xff0c;30% 的营销内容将由生成式人工智能创建并由人类增强。 然而&#xff0c;像三星这样的公司已经发现&#xff0c;不了解新技术风险的用户正在成…

linux(线程控制)

目录&#xff1a; 1.线程创建 2.线程等待 3.线程终止 4.线程分离 5.线程ID -------------------------------------------------------------------------------------------------------------------------------- 1.线程创建 pthread_create pthread_t *pthread 是一个输出型…

Nucleo-F411RE (STM32F411)LL库体验 5 - 通用定时器TIM2的使用

Nucleo-F411RE &#xff08;STM32F411&#xff09;LL库体验 5 - 通用定时器TIM2的使用 1、简述 设定TIM2&#xff0c;计数周期为10KHZ&#xff0c;即计时1s需要10000次&#xff0c;通过shell命令动态修改reload值&#xff0c;来更改定时器的频率。 假定设定TIM2 counter cloc…

第五章 部署PKI与证书服务

❄️作者介绍&#xff1a;奇妙的大歪❄️ &#x1f380;个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01;&#x1f380; &#x1f43d;个人简介&#xff1a;云计算网络运维专业人员&#x1f43d; 前言 PKI&#xff08;公钥加密基础结构&#xff09;&#xff1a;通…

【C++】list的使用和模拟实现

目录 1.什么是list2.list的一些接口3.list的模拟实现3.1 迭代器3.2 list主体3.2.1 构造函数3.2.2 拷贝构造、赋值重载3.2.3 主体内引入迭代器3.2.4 insert和erase3.2.5 clear和析构函数 3.3 const迭代器的实现3.4 实现迭代器的operator-> 4.总结list迭代器的实现 1.什么是li…

领域驱动应用架构实践

一个合适的应用架构不仅能促使项目朝着好的方向发展&#xff0c;易于维护&#xff0c;也能指导团队成员有效协作。 DDD是站在领域的角度来驱动应用架构的落地&#xff0c;接下来将介绍一种落地方案。 架构分层 首先在架构层次方面&#xff0c;在遵循DDD的分层架构模式的同时&…

STM32单片机(六)TIM定时器 -> 第五节:TIM输入捕获

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

Nucleo-F411RE (STM32F411)LL库体验 4 -Letter Shell移植与调试

Nucleo-F411RE &#xff08;STM32F411&#xff09;LL库体验 4 -Letter Shell移植与使用 1、串口的初始化 Nucleo-F411RE自带st-link&#xff0c;并支持虚拟串口的功能&#xff0c;根据原理图&#xff0c;st-link的rx tx接到了Nucleo-F411RE的PA2 PA3&#xff0c;所以我们要初…

以太网MII、RMII、GMII、RGMII(三)

目录 一、MII 二、RMII 三、GMII 四、RGMII 以太网硬件主要包括OSI的最下面两层&#xff0c;物理层和数据链路层&#xff0c;前者的芯片为PHY&#xff0c;后者的芯片为MAC控制器。而MAC与PHY之间的常用的数据传输接口有MII、RMII、GMII、RGMII。 模式 时钟 位宽 速率 M…

pytorch笔记:transformer 源码

来自B站视频&#xff0c;API查阅&#xff0c;TORCH.NN seq2seq 可以是 CNN&#xff0c;RNN&#xff0c;transformer nn.Transformer 关键源码&#xff1a; encoder_layer TransformerEncoderLayer(d_model, nhead, dim_feedforward, dropout,activation, layer_norm_eps, ba…

5.vue3医疗在线问诊项目 - _极速问诊-前置准备 ==> 需求分析、枚举类型、pinia仓库的初始化

5.vue3医疗在线问诊项目 - _极速问诊-前置准备 > 需求分析、枚举类型、pinia仓库的初始化 极速问诊-需求分析{#consult-product} 极速问诊阶段流程分析 线下看病流程&#xff1a; 选择医院&#xff08;三甲、普通&#xff09;》挂号》选择科室 》选择医生&#xff08;专家…

牛客网专项练习——C语言错题集(5)

文章目录 指针的值指针与数组、函数的组合空结构体* 和 的优先级 指针的值 指针的值是一个地址&#xff0c;题目中的字符串 “girl” 应该是 *p 的值&#xff0c;即指针 p 所指地址存储的数据的值。 指针与数组、函数的组合 int *p[n] 等价于 int (*)p[n]&#xff0c;是一个…

xinput1_3.dll丢失怎么办?xinput1_3.dll丢失的修复方法

xinput1_3.dll是电脑文件中的dll文件&#xff08;动态链接库文件&#xff09;。如果计算机中丢失了某个dll文件&#xff0c;可能会导致某些软件和游戏等程序无法正常启动运行&#xff0c;并且导致电脑系统弹窗报错。 在我们打开软件或者游戏的时候&#xff0c;电脑提示xinput1_…

STM32 Proteus仿真自动刹车系统超声波测距电机控制-0042

STM32 Proteus仿真自动刹车系统超声波测距电机控制-0042 Proteus仿真小实验&#xff1a; STM32 Proteus仿真自动刹车系统超声波测距电机控制-0042 功能&#xff1a; 硬件组成&#xff1a;STM32F103C6单片机 LCD1602显示器HCSR04超声波传感器按键(加 减)电机蜂鸣器 1.单片机…

学习Angular的编程之旅

目录 1、简介 2、特点 2.1 横跨多种平台 2.2 速度与性能 2.3 美妙的工具 3、Angular 应用&#xff1a;知识要点 3.1 组件 3.2 模板 3.3 依赖注入 4、与其他框架的对比 1、简介 Angular 是一个应用设计框架与开发平台&#xff0c;旨在创建高效而精致的单页面应用。 A…

Java(二):Spring Boot 项目-文件的增删改查下载

Spring Boot 项目-文件的增删改查下载 准备docker运行mysql设置MySQL时区查看当前MySQL使用的时区MySQL建库建表 定义两个实体类数据表实体类查询条件实体类 工具类com/example/user/utils/FileUtil.java 用到的SQL语句mapper user/src/main/resources/mapper/FileTableDao.xml…