C语言之动态内存管理

news2024/11/16 15:41:25

一、引言

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

虽然在某些编译器(例如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.coloradmin.cn/o/1309855.html

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

相关文章

HarmonyOS创建属性动画

属性动画的使用 1 概述 属性动画&#xff0c;是最为基础的动画&#xff0c;其功能强大、使用场景多&#xff0c;应用范围较广。常用于如下场景中&#xff1a; 一、页面布局发生变化。例如添加、删除部分组件元素。二、页面元素的可见性和位置发生变化。例如显示或者隐藏部分…

RHEL8中ansible的安装

本章主要介绍在RHEL8中如何安装ansible ansible是如何工作的在RHEL8中安装ansible 1.1 ansible的工作原理 如果管理的服务器很多&#xff0c;如几十台甚至几百台&#xff0c;那么就需要一个自动化管理工具了&#xff0c; ansible就是这样的一种自动化管理工具 ansible是通过…

用print太慢了!强烈推荐这款Python Debug工具~

作为程序员&#xff0c;我们都深知调试&#xff08;Debug&#xff09;在编程过程中的重要性。然而&#xff0c;使用传统的"print"语句进行调试可能效率较低&#xff0c;今天&#xff0c;笔者将推荐一款独具一格的Python调试工具——Reloadium。Reloadium为IDE添加了热…

【PyTorch】卷积神经网络

文章目录 1. 理论介绍1.1. 从全连接层到卷积层1.1.1. 背景1.1.2. 从全连接层推导出卷积层 1.2. 卷积层1.2.1. 图像卷积1.2.2. 填充和步幅1.2.3. 多通道 1.3. 池化层&#xff08;又称汇聚层&#xff09;1.3.1. 背景1.3.2. 池化运算1.3.3. 填充和步幅1.3.4. 多通道 1.4. 卷积神经…

Google Tag Manager账号的创建

1&#xff0c;通过官网链接进入主页面 点击创建账号 官网链接-https://www.tagmanager.google.com 2&#xff0c;按提示填写账号名称 账号名称建议填写网址&#xff0c;或者公司名称&#xff0c;全称简称都可&#xff0c; 国家地区按默认的就好不用特地更改&#xff0c; 不…

MySQL进阶2 - 索引

MySQL进阶1 - 索引 1. 索引概述2. 索引结构2.1 二叉树2.2 B-Tree(多路平衡查找树)2.3 BTree2.4 Hash 3. 索引分类4. 索引语法5. SQL性能分析5.1 SQL执行频率5.2 慢查询日志5.3 profile5.4 explain执行计划5.3.1 EXPLAIN执行计划各字段含义&#xff1a; 6. 索引使…

vue实现滑动验证

效果图&#xff1a; 源码地址&#xff1a;github文档地址&#xff1a; https://github.com/monoplasty/vue-monoplasty-slide-verify 使用步骤&#xff1a;1&#xff0c;安装插件&#xff1a; npm install --save vue-monoplasty-slide-verify 在main.js中使用一下&#xff…

记一次跨入smartKettle大门随即转身就走的简单体验过程

目录 &#x1f4da;第一章 背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 源码解读&#x1f4d7;官方说明&#x1f4d7;controller接口&#x1f4d5;swagger&#x1f4d5;源码 &#x1f4d7;Kettle API &#x1f4da;第三章 总结⁉️问题记录❓问题一&#…

使用CFimagehost源码自建无需数据库支持的PHP图片托管服务

文章目录 1.前言2. CFImagehost网站搭建2.1 CFImagehost下载和安装2.2 CFImagehost网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

SpringCloud系列(六)| 聊聊负载均衡

一、负载均衡概述 上一篇文章中&#xff0c;我们在集成OpenFeign的过程中提示我们需要加入了一个依赖就是&#xff1a; spring-cloud-starter-loadbalancer。 顾名思义&#xff0c;这个包的作用就是用来做负载均衡的。 简单解释一下什么是负载均衡&#xff0c;就是当我们的服…

Python面经【11】- Python可迭代对象一网打尽专题

Python面经【11】- Python可迭代对象一网打尽专题 可迭代对象Python的迭代器、生成器1) 迭代器2) 生成器 可迭代对象、迭代器的区别12. 生成器、迭代器的区别什么是装饰器&#xff1f;函数装饰器有什么作用一句话解释什么样的语言能够使用装饰器Python中的作用域&#xff1f;什…

节流防抖:提升前端性能的秘密武器(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

我常用的几个经典Python模块

Python常用的模块非常多&#xff0c;主要分为内置模块和第三方模块两大类&#xff0c;且不同模块应用场景不同又可以分为文本类、数据结构类、数学运算类、文件系统类、爬虫类、网络通讯类等多个类型。 大家常用的内置模块比如&#xff1a;math、re、datetime、urllib、os、ra…

自动化测试流程详解

最近很多小伙伴问我自动化测试到底该怎么做&#xff1f;流程是什么样的&#xff1f;在每个阶段都需要注意什么&#xff1f;本文也就主要从自动化测试的基本流程入手&#xff0c;对面试自动化测试工程师的同学会有不少帮助。对于在职的朋友&#xff0c;也可以参考此流程&#xf…

AI扩图哪家强?我们实地测试了5款扩图工具

AI扩图的命运齿轮开始转动了。 近日&#xff0c;“AI扩图”在各个社交平台上频频出圈&#xff0c;#AI扩图#话题&#xff0c;在抖音平台累计播放数超7.8亿次。 何为AI扩图&#xff1f; AI 扩图功能给我们带来了一个观察世界的新角度。在 AI 的加持下&#xff0c;我们可以看到…

门窗企业网站建设作用是什么

门窗作为市场重要的组合部分&#xff0c;其应用广泛使得众多商家入局经营&#xff0c;无论大型建筑还是家庭应用&#xff0c;都有较高需求度&#xff0c;尤其对品牌商来说&#xff0c;无论直售还是加盟都可以获得不菲效益。 但对门窗企业来说&#xff0c;也需要解决几个痛点&a…

【计算思维】第14届蓝桥杯省赛计算思维U8组真题试卷

选择题 第 1 题 单选题 要把下面 4 张图片重新排列成蜗牛的画像&#xff0c;该如何排列这些图片?( ) A. B. C. D. 第 2 题 单选题 下图的几张牌&#xff0c;每次可以交换任意 2 张。 如将它们按照下面的顺序排列&#xff0c;最少需要交换( )次。 A.4 B.5 C.6 D.7 …

YOLOv8改进 | 2023Neck篇 | BiFPN双向特征金字塔网络(附yaml文件+代码)

一、本文介绍 本文给大家带来的改进机制是BiFPN双向特征金字塔网络&#xff0c;其是一种特征融合层的结构&#xff0c;也就是我们本文改进YOLOv8模型中的Neck部分&#xff0c;它的主要思想是通过多层级的特征金字塔和双向信息传递来提高精度。本文给大家带来的结构可以让大家自…

2023年贺岁电影:一眼多,二眼好多

如果从11月末开始统计&#xff0c;今年贺岁档共有72部贺岁片&#xff0c;平均一天就有2部电影上映&#xff0c;看完总计需要花费7400分钟。 这个数量几乎快赶上2021年到2022年贺岁片的总和。 今年电影市场快速回暖以来&#xff0c;多部爆款作品接力上映&#xff0c;持续刺激市…

项目篇 | 图书管理系统 | 图像加载与绘制

项目篇 | 图书管理系统 | 图像加载与绘制 基本介绍 首先解释清楚什么叫图像加载与绘制,意思就是说项目中需要用到一些图片资源(各种图标),我们要在图书管理系统中展示这些图片,就需要先导入图片到项目中,再加载图片资源(通过资源路径)、绘制图片(即展示)。 注:如果…