C语言之动态内存管理

一、引言

当我们写了一段程序,创建了一个变量或者一个数组,这些操作都需要在内存中开辟出一块空间。但是我们过去的这些操作有一定的局限性:开辟的空间大小是固定的,并且数组在申明的时候,必须指定数组的长度,数组空间一旦确定大小就无法再调整了。

虽然在某些编译器(例如gcc)中。允许我们使用一个变量来指定数组的大小,但是在大部分编译器中这种变长数组都是不允许的。所以C语言引入了动态内存的开辟方式,让程序员可以自己申请和释放空间,这种方法就比较的灵活了。


二、malloc函数和free函数

2.1 malloc函数

首先明确一点:在使用动态内存管理函数的时候要包含头文件 <stdlib.h>

C语言给我们提供了这么一个动态内存开辟的函数:

void* malloc ( size_t size );

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

注意!

  • 如果malloc开辟成功,则会返回一个指向开辟出的这块空间的指针;如果malloc开辟失败,则会返回一个NULL指针,因此:malloc的返回值一定要作检查
  • 返回的指针的类型为 void*,所以我们在使用malloc的时候需要对其返回的指针进行强制类型转换,具体类型由我们的需求而定
  • 如果malloc的参数为0,这种情况是标准未定义的,具体执行情况看编译器 

2.2 free函数

为什么要把malloc和free放在一起讲呢?当你未来在写代码时使用了malloc函数或者其他动态内存管理函数的时候,就必须要用到free函数,接下来我们具体讲解一下

free函数是C语言专门用来做动态内存的释放和回收的函数,其原型如下:

void free ( void* ptr );

free函数可以用来释放我们通过malloc动态开辟的内存空间,如果一块动态开辟的内存空间没有被free函数释放的话,就会造成内存泄漏

注意!

  • 如果参数 ptr 指向的空间不是动态内存开辟的则会报错
  • 如果参数 ptr 为 NULL ,则free函数什么都不做

free函数和malloc函数一样要通过<stdlib.h>头文件声明

学会了这两个函数之后,我们就可以开始练习写代码了

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int *arr = (int *)malloc(sizeof(int) * 10);
	if (arr == NULL)
	{
		perror("malloc fail");
		return 1;
	}
	free(arr);
	arr = NULL; // 为什么要置空?
	return 0;
}

上面,指针arr指向了我们动态开辟的一块40个字节的空间,然后我们free掉这一块空间后,又给arr置空了,为什么要这么做呢?

实际上,arr指向的空间被释放掉后就变成了野指针,为了防止错误的操作,我们就对其进行置空防止后续有人错误使用


三、calloc函数

C语言还提供了calloc函数用来进行动态内存分配,这个函数也和malloc很相似

void* calloc ( size_t num , size_t size );

calloc函数可以为 num 个大小为 size 的元素开辟一块空间,并且把这块空间的每个字节都初始化为0,这也是它和malloc最大的区别

例如:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int *arr = (int *)calloc(10, sizeof(int));
	if (arr == NULL)
	{
		perror("calloc fail");
		return 1;
	}
	free(arr);
	arr = NULL;
	return 0;
}

输出结果为

所以如果我们需要对动态开辟的内存初始化的话,calloc是更好的选择


四、realloc函数

动态内存管理就是让我们更加自由的去开辟内存,但是光看上面几个函数似乎还不够自由。这里就引入一个动态内存管理的好帮手:realloc函数。这个函数的出现让动态内存管理更加灵活。

当我们动态开辟内存之后,有时会发现开辟出的空间太小了不够用,有时又会发现开辟出的空间太大了有点浪费。为了合理的使用内存,对内存的大小做灵活的调整,就需要使用realloc函数来对动态开辟的内存大小进行调整。

void* realloc ( void* ptr , size_t size );

其中,ptr 是待调整大小的内存空间的地址,size 是调整之后的新大小,返回值是调整后的内存的起始位置地址。

realloc在调整内存空间时存在两种情况:

  1. 原空间后面有足够大的空间,可以在原有内存之后直接增加新的空间,原空间的数据不发生变化
  2. 原空间后面没有足够大的空间,就会在内存的堆区重新找一块能够容纳新空间的位置,同时把旧空间的数据拷贝到新空间,然后对旧空间进行释放并返回新空间的起始地址

这里引入一个问题:下面的代码1和代码2哪个更好呢?

//代码1
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int *arr = (int *)malloc(sizeof(int) * 10);
	arr = (int *)realloc(arr, 1000);
	free(arr);
	arr = NULL;
	return 0;
}
//代码2
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int *arr = (int *)malloc(sizeof(int) * 10);
	int *tmp = (int *)realloc(arr, 1000);
	if(tmp!=NULL)
	{
		arr = tmp;
	}
	free(arr);
	arr = NULL;
	return 0;
}

代码2更好。实际上,当我们使用realloc函数调整动态开辟的内存大小的时候,是存在失败的可能的。如果我们直接对指向原空间的指针来进行调整,一旦失败则会返回NULL,指针无法再指向原空间,则原空间就无法被释放,造成内存泄漏

所以在代码2中,我们使用一个tmp指针来进行调整,调整成功后再赋给arr,这样就更保险。

五、常见的动态内存管理的错误

5.1 对NULL指针的解引用操作

void test()
{
	int *arr = (int *)malloc(sizeof(int) * 10);
	*arr = 20;
	free(arr);
}

如果malloc开辟失败,则arr的值为NULL,再对其解引用就会造成错误

所以使用malloc、calloc、realloc等函数的时候,最好增加一个检测环节避免对NULL指针错误使用

5.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);
}

我们用malloc开辟了一块10个sizeof(int)的空间,但是在循环中却访问了第11个位置,属于越界访问,也是错误的做法

5.3 对非动态开辟的内存使用free释放

上面提到过,只有动态开辟的内存才能用free将其释放,如果我们用free释放非动态开辟的内存就会造成错误,如下:

void test()
{
	int i = 0;
	int *p = &i;
	free(p);
}

5.4 使用free不完全释放动态开辟的内存

void test()
{
	int *p = (int *)malloc(100);
	p++;
	free(p);
}

如上,p++之后不再指向这块动态开辟的空间的起始地址,所以free函数无法对其完全释放。所以当我们使用free函数释放内存的时候需要注意指针是否指向这块内存的起始位置。

5.5 对一块动态内存重复释放

void test()
{
	int *p = (int *)malloc(100);
	free(p);
	free(p);
}

对已经被释放的动态内存进行二次释放也是错误的做法

5.6 不释放动态开辟的内存

前面说过当我们动态开辟了一块内存之后,一定要在程序中的某个位置把它free掉,不然就会造成内存泄漏

六、柔性数组

在C99中,结构体的最后一个成员允许是位置大小的数组,这个就叫柔性数组成员

例如:

typedef struct
{
	int i;
	int a[0];//柔性数组成员
} type_a;

当然这么做有些编译器会报错,可以改成

typedef struct
{
	int i;
	int a[];
} type_a;

柔性数组的特点有:

  • 结构体中的柔性数组成员前面必须至少有一个其他成员
  • sizeof返回结构体的大小时,不包括柔性数组成员
  • 包含柔性数组成员的结构体要用malloc进行内存的动态分配,并且分配的内存大小要大于结构体的大小,以应对柔性数组的预期大小

我们可以看看上面那个结构体的大小是多少

可以看到刚好是第一个成员的大小,不包含下面的柔性数组成员

完.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DeepMind的最新研究:人类最后的自留地失守了?

AI对人类世界的学习能力&#xff0c;到目前为止仍然停留在语言层面。 喂给大模型语料——最初是维基百科和Reddit&#xff0c;后来扩展到音频、视觉图像甚至雷达和热图像——后者广义上说是换了种表达方式的语言。也因此有生成式AI的创业者认为&#xff0c;一个极度聪明的大语…

EasyRecovery2024苹果电脑mac破解版安装包下载

EasyRecovery是一款操作安全、价格便宜、用户自主操作的非破坏性的只读应用程序&#xff0c;它不会往源驱上写任何东西&#xff0c;也不会对源驱做任何改变。它支持从各种各样的存储介质恢复删除或者丢失的文件&#xff0c;其支持的媒体介质包括&#xff1a;硬盘驱动器、光驱、…

Pytorch的安装

Pytorch的安装 Pytorch的安装查看显卡信息CUDA兼容性安装说明开始安装常见异常安装CUDA Pytorch的安装 PyTorch的安装绝对是一个不是那么简单的过程&#xff0c;或多或少总是会出现一些奇奇怪怪的问题&#xff0c;这里分享记录一下PyTorch的安装心得。 查看显卡信息 没用显卡的…

Tomcat部署Activiti官方 流程设计器【数据库更换为Mysql !!!】

一、官网下载activiti6 解压后结构如下: database&#xff1a; 存放数据库对象相关脚本&#xff0c;包含不同的数据库脚本 libs&#xff1a; 包含activiti开发过程中需要用到的jar包和源码&#xff0c;不建议通过jar包直接引用&#xff0c;建议通过maven进行管理 wars&am…

模块一——双指针:202.快乐数

文章目录 题目描述简单证明补充知识算法原理代码实现 题目描述 题目链接&#xff1a;202.快乐数 为了方便叙述&#xff0c;将对于⼀个正整数&#xff0c;每⼀次将该数替换为它每个位置上的数字的平方和这⼀个操作记为x操作&#xff1b; 题目告诉我们&#xff0c;当我们不断重…

RHEL8_Linux使用podman管理容器

本章主要介绍使用 podman 管理容器 了解什么是容器&#xff0c;容器和镜像的关系安装和配置podman拉取和删除镜像给镜像打标签导出和导入镜像创建和删除镜像 1.了解容器及和镜像的关系 对于初学者来说&#xff0c;不太容易理解什么是容器&#xff0c;这里举一个例子。想象一下…

Leetcode69 x的平方根

x的平方根 题解1 袖珍计算器算法题解2 二分查找题解3 牛顿迭代 给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&…

KubeKey 离线部署 KubeSphere v3.4.1 和 K8s v1.26 实战指南

作者&#xff1a;运维有术 前言 知识点 定级&#xff1a;入门级了解清单 (manifest) 和制品 (artifact) 的概念掌握 manifest 清单的编写方法根据 manifest 清单制作 artifactKubeKey 离线集群配置文件编写KubeKey 离线部署 HarborKubeKey 离线部署 KubeSphere 和 K8sKubeKey…

DBSCAN聚类算法学习笔记

DBSCAN聚类算法学习笔记 一些概念名词 MinPts&#xff1a;聚类在一起的点的最小数目&#xff0c;超过这一阈值才算是一个族群 核心点&#xff1a;邻域内数据点超过MinPts的点 边界点&#xff1a;落在核心点邻域内的点称为边界点 噪声点&#xff1a;既不是核心点也不是边界点的…

【Spring】01 Bean 介绍

文章目录 1. 定义2. 特性1&#xff09;可重用性2&#xff09;可配置性3&#xff09;可管理性 3. 生命周期1&#xff09;实例化2&#xff09;属性设置3&#xff09;初始化4&#xff09;使用5&#xff09;销毁 4. 配置方式1&#xff09;XML配置2&#xff09;注解配置3&#xff09…

docker-compose Install gitea

gitea 前言 Gitea 是一个轻量级的 DevOps 平台软件。从开发计划到产品成型的整个软件生命周期,他都能够高效而轻松的帮助团队和开发者。包括 Git 托管、代码审查、团队协作、软件包注册和 CI/CD。它与 GitHub、Bitbucket 和 GitLab 等比较类似。 Gitea 最初是从 Gogs 分支而来…

【揭秘】企业自建社群商城:小程序自主经营的成功秘诀!

在当今这个数字化的时代&#xff0c;社群电商已经成为了商业领域的一个重要趋势。社群电商是指通过社交媒体平台&#xff0c;将具有共同兴趣、需求或价值观的人们聚集在一起&#xff0c;形成一个社群&#xff0c;然后通过提供产品或服务来满足这些人的需求。这种商业模式不仅可…

脚本测试postman快速导出python接口测试过程示例

Postman的脚本可以导出多种语言的脚本&#xff0c;方便二次维护开发。 Python的requests库&#xff0c;支持python2和python3&#xff0c;用于发送http/https请求 使用unittest进行接口自动化测试 01、环境准备 1、安装python&#xff08;使用python2或3都可以&#xff09;…

自学编程推荐一个容易学的中文编程工具,构件箱之单选框组简介

一、前言&#xff1a; 零基础自学编程&#xff0c;中文编程工具下载&#xff0c;中文编程工具构件之扩展系统菜单构件教程 编程系统化教程链接https://jywxz.blog.csdn.net/article/details/134073098?spm1001.2014.3001.5502 给大家分享一款中文编程工具&#xff0c;零基础轻…

dialog 在xml文件进行了自适应宽,但是失效了

如下图 讲述了为什么已经设置好了dialog的宽高 到了显示的时候就会失效的原因 解决方式 &#xff1a; 在自定的dialog中的onstart()方法中进行重新设置宽高 Window window getWindow();WindowManager.LayoutParams lp window.getAttributes();lp.height LinearLayout.La…

springboot使用EasyExcel导入数据

springboot使用EasyExcel导入数据 1. 引入依赖 <!-- Easy Excel --> <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.2.1</version> </dependency>2. 建立对应实体类 假如…

Visual Studio使用Web Deploy发布.NET Web应用到指定服务器的IIS中

前言 今天要讲的是在Window 2008 R2版本的服务器下如何配置Web Deploy&#xff0c;和Visual Studio使用Web Deploy发布.NET Web应用到指定服务器的IIS中。 因为历史原因项目只能使用这个版本的服务器&#xff0c;当然使用其他服务器版本配置流程也是一样的。 Web Deploy介绍 …

Oracle数据库对SAP的支持

其实有时候&#xff0c;很多信息都已经整理好了&#xff0c;你只需要知道他在哪里就好&#xff0c;无需自己整理。 Oracle数据库对SAP的支持&#xff0c;可以从这个网页快速了解。 看前面的概述&#xff1a; Oracle 数据库是全球 SAP 客户中排名第一的数据库&#xff0c;拥有…

插入算法(C语言)

#include<cstdio> #include<iostream> #define N 9 using namespace std; int main() {int arr[N1] { 1,4,7,13,16,19,22,25,280 }; int in,i,j;//要插入的数字//打印要插入数字的数组所有元素printf("插入前的数组: ");for ( i 0; i <N; i){print…

设计模式——单例模式(创建型)

引言 单例模式是一种创建型设计模式&#xff0c; 让你能够保证一个类只有一个实例&#xff0c; 并提供一个访问该实例的全局节点。 问题 单例模式同时解决了两个问题&#xff0c; 所以违反了单一职责原则&#xff1a; 保证一个类只有一个实例。 为什么会有人想要控制一个类所…