C语言——动态内存管理(malloc, calloc, realloc, free, 柔性数组详解)

news2025/1/19 11:05:54

C语言——动态内存管理

1. 为什么需要动态内存管理

我们以往定义数组,都是这么定义的:

int nums[10] = {0};

以这种方式开辟空间有两个特点:

  1. 空间开辟的大小是固定的
  2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配

因此就导致了这样一个现象:我们无法在后续的过程中修改数组的大小,这是一个十分麻烦的事情

而为了解决这个问题,我们就需要学习动态内存开辟了

2. 动态内存函数的介绍

注:需要头文件<stdlib.h>

需要知道,和静态开辟空间不一样,计算机是在堆上开辟的动态空间

2.1 malloc

void* malloc (size_t size);

这个函数向内存申请一块大小为size字节的连续可用的空间,并返回指向这块空间的指针

  • 如果开辟成功,则返回一个指向这块空间的指针
  • 如果开辟失败,则返回一个空指针(NULL),因此我们一定要对malloc的返回值作有效性的判断
  • 返回值为void *,因此当我们用指针变量接受这个返回值时,我们要将这个返回值强制转换为需要的类型
  • 如果参数size为0,malloc的行为是标准未定义的,

例如:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    //向内存申请40个字节的空间,并将返回的指针强制转换成int*型,并将其赋予指针nums
    int *nums = (int*)malloc(sizeof(int) * 10);	

    //检验返回值的有效性
    if (nums == NULL)
    {
        perror("malloc");
        return 1;
    }

    //循环打印nums指向空间的值
    for (int i = 0; i < 10; i++)
        printf("%d\n", *(nums + i));
    
    free(nums);
    nums = NULL;
    
    return 0;
}

output:

-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
  • 这说明,malloc申请到空间后,是不会对该空间初始化的

2.2 free

需要注意:凡是动态申请的内存,除非整个程序结束,申请的内存是不会主动归还给系统的,为了避免内存泄漏,我们应该使用函数free来将申请的内存释放

void free (void* ptr);

free函数用来释放动态开辟的内存

  • 如果参数``ptr指向的空间不是动态开辟的,那free`函数的行为是未定义的
  • 如果参数ptr是NULL指针,则函数什么事都不做
  • 正常释放后,ptr指向不明,称为野指针,因此,应该置为空(NULL)

例如:

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

int main()
{
    //向内存申请40个字节的空间,并将返回的指针强制转换成int*型,并将其赋予指针nums
    int *nums = (int*)malloc(sizeof(int) * 10);	

    //检验返回值的有效性
    if (nums == NULL)
    {
        perror("malloc");
        return 1;
    }

    free(nums);	//释放ptr所指向的动态内存
    nums = NULL;	//将野指针置空
    
    return 0;
}

2.3calloc

void* calloc (size_t num, size_t size);
  • 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
  • 与函数malloc的区别只在于calloc会在返回指针之前把申请的空间的每个字节初始化为0

例如:

##include<stdio.h>
#include<stdlib.h>
int main()
{    
	//申请10个大小为4的空间,并让指针nums指向它
    int* nums = (int*)calloc(10, sizeof(int));

    //判断返回值的有效性
    if(nums == NULL)
    {
        perror("calloc");
        return 1;
    }

    //打印nums指向空间的元素
    for (int i = 0; i < 10; i++)
        printf("%d\n", *(nums + i));
    
    
    free(nums);
    nums = NULL;
    
    return 0;
}

output:

0
0
0
0
0
0
0
0
0
0

2.4 realloc

所谓的动态内存管理,“内存管理”我们好像已经会了,那这个“动”又是怎么做到的呢?我们前面所学的malloc, calloc好像并不能让申请的内存动起来呀。

要想实现对内存的增加或减小,就需要我们的函数realloc

void* realloc (void* ptr, size_t size);
  • ptr是要调整的内存地址

    • 如果ptr不为空,那么就会修改ptr所指向空间的大小
    • 如果ptr为空,那么就和malloc的功能相似,会直接返回一个指向大小为size字节空间的指针
  • size为调整之后的大小

  • 返回值为调整之后的内存的起始位置

  • 扩容后的空间不会被初始化

  • 这个函数调整源内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

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

    • 情况一:原有空间之后有足够大的空间,那么就在原有的地方增容,并返回原来的起始地址

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-18eQPqE6-1689339767174)(C:/Users/HUASHUO/AppData/Roaming/Typora/typora-user-images/image-20230714151133794.png)]

    • 情况二:原有空间之后没有足够大的空间,那么就在合适的地方重新开辟一块大小为size的空间,将原来空间的数据拷贝到新空间,再释放掉原来的空间,最后再返回新空间的起始地址

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C3AeLIzI-1689339767175)(C:/Users/HUASHUO/AppData/Roaming/Typora/typora-user-images/image-20230714151338596.png)]

  • 如果调整失败,就会返回空指针,为了考虑到这种情况,我们应该避免以下的代码:

//error example

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

int main()
{
	int* nums = (int*)malloc(10 * sizeof(int));
	if (nums == NULL)
	{
		perror("malloc");
		return 1;
	}

    //如果开辟失败,那么nums就成了空指针,前面nums管理的40个字节的空间就找不到了,这样就造成了内存泄漏
	nums = (int*)realloc(nums, 20 * sizeof(int));
	if (nums == NULL)
	{
		perror("realloc");
		return 1;
	}
	
	free(nums);
	nums = NULL;

	return 0;
}
  • 正确的方式应该是这样的:
//right example

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

int main()
{
	int* nums = (int*)malloc(10 * sizeof(int));
	if (nums == NULL)
	{
		perror("malloc");
		return 1;
	}

    //先用一个中间变量temp接受
	int* temp = (int*)realloc(nums, 20 * sizeof(int));
	if (temp == NULL)
	{
		perror("realloc");
		return 1;
	}
	
    //当temp有效时,再用nums接受
    nums = temp;
    
	free(nums);
	nums = NULL;

	return 0;
}

最后,再对realloc的具体使用举个例子:

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

int main()
{
    //先动态开辟40个字节的内存
	int* nums = (int*)malloc(10 * sizeof(int));
	if (nums == NULL)
	{
		perror("malloc");
		return 1;
	}

    //将这块空间初始化为0
	memset(nums, 0, 10 * sizeof(int));

    //将这块空间扩容到60个字节,并先用中间变量temp接受
	int* temp = (int*)realloc(nums, 15 * sizeof(int));
	if (temp == NULL)
	{
		perror("realloc");
		return 1;
	}

    //确定temp有效后再用nums指向temp
	nums = temp;

    //打印扩容后空间的数据
	for (int i = 0; i < 15; i++)
	{
		printf("%d\n", nums[i]);
	}
	
    //释放内存
	free(nums);
	nums = NULL;

	return 0;
}

output:

0
0
0
0
0
0
0
0
0
0
-842150451
-842150451
-842150451
-842150451
-842150451

3. 常见的关于动态内存开辟的错误

3.1 对NULL指针的解引用操作

void test()
{
    int *p = (int *)malloc(sizeof(int));
    *p = 20;	//如果p为空指针,就会有问题,一定先要检查返回指针的有效性
    free(p)
}

3.2 对动态开辟空间的越界访问

void test()
{
    int *p = (int *)malloc(10 * sizeof(int));
    if(NULL == p)
    {
        perror("malloc");
        return 1;
	}
    for(int i = 0; i <= 10; i++)
        *(p + i) = i;	//当i是10的时候就会越界访问
    
    free(p);
}

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

void test()
{
    int nums[10] = {0};
    free(nums);
}

3.3 对同一块动态内存多次free释放

void test()
{
    int *p = (int *)malloc(10 * sizeof(int));
    if(NULL == p)
    {
        perror("malloc");
        return 1;
	}
    
    free(p);
    free(p);
}

3.4 动态开辟内存忘记释放(内存泄漏)

void test()
{
    int *p = (int *)malloc(10 * sizeof(int));
    if(NULL == p)
    {
        perror("malloc");
        return 1;
	}
    for(int i = 0; i < 10; i++)
        *(p + i) = i;	
}

4. 柔性数组(flexible array)

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

例如:

typedef struct ST
{
	int i;
	int a[0];	//柔性数组成员,也可以写成 a[];
}ST;

4.1 柔性数组的特点

  1. 柔性数组前至少有一个其他成员

  2. sizeof返回的结构体大小不包含结构中柔性数组的内存,例如对于上面的代码:

    printf("%d\n",sizeof(ST));
    

    output:

    4
    
  3. 包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小,同样,也可以用realloc进行增容,例如:

    #include<stdio.h>
    #include<stdlib.h>
    
    typedef struct ST
    {
    	int i;
    	int a[];	//柔性数组成员
    }ST;
    
    int main()
    {
    	ST *st1 = (ST*)malloc(sizeof(ST) + sizeof(int) * 10);	//向内存申请大小为结构大小加10个整型的内存空间
    	if (NULL == st1)
    	{
    		perror("malloc");
    		return 1;
    	}
    
    	st1->i = 10;
    
        //给空间赋值
    	for (int i = 0; i < 10; i++)
    		(st1->a)[i] = i + 1;
    	//打印空间元素
    	for (int i = 0; i < 10; i++)
    		printf("%d\n", (st1->a)[i]);
        
        //增容,将内存扩大5个int型
        ST* temp = (ST*)realloc(st1, sizeof(ST) + sizeof(int) * 15);
    	if (NULL == temp)
    	{
    		perror("realloc");
    		return 1;
    	}
        
        st1 = temp;
    
        //打印空间数据
    	for (int i = 0; i < 15; i++)
    		printf("%d\n", (st1->a)[i]);
    
        //释放动态内存
    	free(st1);
    	st1 = NULL;
    
    	return 0;
    }
    

    由上面的分析我们可以看到,我们完全可以在结构体里面创建一个整形指针(其他类型也可以),然后对其进行动态内存开辟就可以完全替代柔性数组的功能,因此柔性数组这一功能并不常用,我们仅作了解即可

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

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

相关文章

数据库应用:Navicat连接MySQL

目录 一、理论 1.Navicat 2.MVCC 二、实验 1.Navicat连接MySQL 2.navicat的基础操作 3.测试提交事务 三、问题 1.解决1130 2.解决2003 四、总结 一、理论 1.Navicat &#xff08;1&#xff09;简介 Navicat Premium 是一套数据库开发工具&#xff0c;实现从单一应用…

基于时域特征和频域特征组合的敏感特征集,再利用CNN进行轴承故障诊断(python编程)

1.文件夹介绍&#xff08;使用的是CWRU数据集&#xff09; 0HP-3HP四个文件夹装载不同工况下的内圈故障、外圈故障、滚动体故障和正常轴承数据。 2.模型 按照1024的长度分割样本&#xff0c;构建内圈故障、外圈故障、滚动体故障和正常轴承样本集 2.1.计算11种时域特征值 # 计…

EfficientNet论文笔记

EfficientNet论文笔记 通过NAS平衡了channel&#xff0c;depth&#xff0c;resolution&#xff0c;发现在相同的FLOPs下&#xff0c;同时增加 depth和 resolution的效果最好。 数据集效果小于resolution怎么办&#xff1f; EfficientNet—b0框架 表格中每个MBConv后会跟一个…

Maven —— 项目管理工具

前言 在这篇文章中&#xff0c;荔枝会介绍如何在项目工程中借助Maven的力量来开发&#xff0c;主要涉及Maven的下载安装、环境变量的配置、IDEA中的Maven的路径配置和信息修改以及通过Maven来快速构建项目。希望能对需要配置的小伙伴们有帮助哈哈哈哈~~~ 文章目录 前言 一、初…

力扣刷题序列 - 字符串篇

这里写目录标题 字符1.520 回文串的定义2. 125 公共前缀3. 14 单词4. 4345. 58 字符串的反转6. 3447. 5418. 5579. 151 字符的统计10. 38711. 38912. 38313. 24214. 4915. 45116. 42317. 65718. 55119. 69620. 467 数字与字符间的转换21.41222.50623.53924.553537592---64038 子…

GAME101 OpenCV环境安装

文章目录 Opencv 库编译Step 1.下载源码Step 2. 使用CMake编译Step3. 解决CMake 过程重的报错错误1&#xff1a; 错误的Python版本:错误1 解决办法 错误2&#xff1a;下载ippicv_2020_win_ia32_20191018_general.zip失败错误2 解决办法 错误3&#xff1a;ffmpeg相关文件下载失败…

力扣 509.斐波那契数

509.斐波那契数 1 题目2 思路3 代码4 结果 1 题目 题目来源&#xff1a;力扣&#xff08;LeetCode &#xff09;https://leetcode.cn/problems/fibonacci-number 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为斐波那契数列 。该数列由 0 和 1 开始&…

git国内下载

https://npm.taobao.org/mirrors/git-for-windows/点进去最新的最后一条 选择.exe文件点击

二、遥感物理基础(2)物体的发射与反射辐射特征

前言 本文内容较为枯燥&#xff0c;是遥感的物理原理&#xff0c;作者已经尽量去帮助读者理解了&#xff0c;无论是精细的阅读还是走马观花&#xff0c;长期下来都能提高读者对专业知识的理解&#xff1b;作者非物理专业&#xff0c;对某些知识点的总结仅是个人理解&#xff0c…

win7 刻录机刻录文件显示 “准备好写入到”光盘中的文件”

一、问题描述 就是这么突然&#xff0c;好好的刻录机&#xff0c;突然就刻录不了了&#xff0c;昨天都可以正常刻录&#xff0c;今天就显示&#xff1a; 准备好写入到”光盘中的文件&#xff0c;然后还显示待刻录的文件…&#xff0c;右键选择刻录就直接弹出光盘了&#xff1f…

吴恩达机器学习2022-Jupyter特征缩放

1可选实验室: 特征缩放和学习率(多变量) 1.1 目标 在这个实验室里: 利用前一实验室开发的多变量线性回归模型程序在具有多种功能的数据集上运行梯度下降法探讨学习速度 alpha 对梯度下降法的影响通过使用 z 分数标准化的特征缩放来提高梯度下降法的性能 1.2 工具 您将使用…

第七章:FCN——Fully Convolutional Networks for Semantic Segmentation

0.摘要 卷积神经网络是强大的视觉模型&#xff0c;能够产生特征的层级结构。我们展示了通过端到端、像素到像素的训练的卷积神经网络在语义分割方面超越了现有技术的最新成果。我们的关键发现是构建“全卷积”网络&#xff0c;它可以接受任意大小的输入并生成相应大小的输出&am…

OSPF和VLAN综合实验

目录 题目 1.IP地址的规划设计 2.搭建拓扑并进行基础IP配置 3.配置虚拟局域网 1&#xff09;按子网划分要求配置PC1和PC2 检测&#xff1a;输入[SW1]display vlan进行检查 配置路由器R3 检测&#xff1a;用PC1去访问PC2 2&#xff09;配置拓扑中其余路由器的网关以及回…

基于安森美音频处理器Ezairo 8300助听器设计

v hezkz17进数字音频系统研究开发交流答疑 Ezairo 8300 系统框图 许多现代音频产品将受益于安森美(onsemi)基于DSP的EZAIRO系列音频处理器。 安森美&#xff08;ON Semiconductor&#xff09;的EZAIRO系列音频处理器是基于数字信号处理器&#xff08;DSP&#xff09;的产品系列…

​​Layui之用户管理实例(对数据的增删改查)

目录 ​编辑一、R工具介绍&#xff08;&#xff09; ​编辑二、数据表的增删改查 ​编辑2.1我们先得从查询数据库的语句入手 2.2优化dao类 2.4UserAction类 2.5前台的页面实现增删改查操作 2.6 userManage页面JS 2.7user新增、修改iframe层js 前言 上一篇我分享了…

http1.0、http1.1 http 2.0

HTTP/1.0是无状态、无连接的应用层协议。 无连接 无连接&#xff1a;每次请求都要建立连接&#xff0c;需要使用 keep-alive 参数建立长连接、HTTP1.1默认长连接keep-alive   无法复用连接&#xff0c;每次发送请求都要进行TCP连接&#xff0c;TCP的连接释放都比较费事&…

vue-next-admin vue3.x版本,table自定义

vue3.x版本&#xff0c;将table进行了封装。使用起来更方便了。但是&#xff0c;有时候我们需要将一组信息显示到一列中。所以我将其进行了简单的二次改造。支持table-column自定义。 table改造代码 <template><div class"table-container"><el-tabl…

R语言的水文、水环境模型优化技术及快速率定方法与多模型案例实践

在水利、环境、生态、机械以及航天等领域中&#xff0c;数学模型已经成为一种常用的技术手段。同时&#xff0c;为了提高模型的性能&#xff0c;减小模型误用带来的风险&#xff1b;模型的优化技术也被广泛用于模型的使用过程。模型参数的快速优化技术不但涉及到优化本身而且涉…

高速入门知识03:路阻抗匹配方法

文章目录 前言一、简单并行匹配二、戴维南并行匹配三、主动并行匹配四、串联RC并行匹配五、串联匹配六、差分对匹配七、收发器片内匹配 前言 高速电路阻抗匹配方法 阻抗不匹配会导致信号在传输线上来回反射&#xff0c;使负载接收器出现振铃。振铃降低了接收 器的动态范围&am…

Centos使用docker部署nacos

Centos使用docker部署nacos 对于使用Docker部署Nacos&#xff0c;您可以按照以下步骤进行操作&#xff1a; 在您的服务器上安装Docker和Docker Compose。创建一个用于存储Nacos数据的目录&#xff0c;例如/path/to/nacos/data。创建一个docker-compose.yml文件&#xff0c;并…