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

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*型,并将其赋予指针numsint *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*型,并将其赋予指针numsint *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;}//将这块空间初始化为0memset(nums, 0, 10 * sizeof(int));//将这块空间扩容到60个字节,并先用中间变量temp接受int* temp = (int*)realloc(nums, 15 * sizeof(int));if (temp == NULL){perror("realloc");return 1;}//确定temp有效后再用nums指向tempnums = 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.hqwc.cn/news/22137.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

scrapy---爬虫界的django

1介绍 scrapy架构 引擎(EGINE)&#xff1a;引擎负责控制系统所有组件之间的数据流&#xff0c;并在某些动作发生时触发事件。大总管&#xff0c;负责整个爬虫数据的流动 调度器(SCHEDULER)用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个U…

引入头文件#include <iostream>的时候发生了什么?

<iostream> namespace std {extern istream cin;extern ostream cout;extern ostream cerr;extern ostream clog;extern wistream wcin;extern wostream wcout;extern wostream wcerr;extern wostream wclog;};cin是什么&#xff1f; cin extern istream cin; The objec…

JVM 中的垃圾回收策略

文章目录 JVM 中的垃圾回收策略死亡对象的判断算法引用计数可达性分析 垃圾回收算法标记-清除算法复制算法标记-整理算法分代算法 JVM 中的垃圾回收策略 C 语言中&#xff0c;malloc 的内存必须 手动 free&#xff0c;否则容易出现内存泄漏&#xff08;光申请内存&#xff0c;…

Docker查看相关存储信息以及扩容

Docker查看相关存储信息以及扩容 &#xff08;mac环境&#xff09; 查看docker基本信息&#xff1a; docker info可以看到docker的存储位置在这里 2. 查看mac的所有盘以及分区大小情况 diskutil listdocker查看网络信息&#xff1a; docker ps # 查看所有在运行的container信…

中信银行西安分行举办金融助力外贸企业“走出去“高端论坛

7月14日&#xff0c;中信银行西安分行联合中国出口信用保险公司陕西分公司、西安市工商联举办"智汇西安、信融全球"——金融助力外贸企业"走出去"高端论坛。该论坛紧跟“加快建设贸易强国”的战略指引&#xff0c;以创新金融服务助力外贸企业融入高水平对外…

第51步 深度学习图像识别:Convolutional Vision Transformer建模(Pytorch)

基于WIN10的64位系统演示 一、写在前面 &#xff08;1&#xff09;Convolutional Vision Transformers Convolutional Vision Transformer&#xff08;ConViT&#xff09;是一种结合了卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称CNN&#xff09…

08_SPI-Flash 扇区擦除实验

08_SPI-Flash 扇区擦除实验 1. 实验目标2. 操作时序2.1 扇区擦除操作指令2.2 完整扇区擦除操作时序 3. 程序框图3.1 顶层框图3.2 扇区擦除模块 4. 波形图5. RTL5.1 flash_se_ctrl5.2 spi_flash_se 6. Testbench6.1 tb_flash_se_ctrl6.2 tb_spi_flash_se 1. 实验目标 编写扇区擦…

Oracle表设计

设计原则 为了建立冗余较小、结构合理的数据库&#xff0c;设计数据库时必须遵循一定的规 则。在关系型数据库中这种规则就称为范式。 范式是符合某一种设计要求的总结。 要想设计一个结构合理的关系型数据库&#xff0c;必须满足一定的范式。在实际开发中最为 常见的设计范式…

学校食堂升级改造?看这篇就够了!

在现代化的食堂管理中&#xff0c;智慧食堂扮演着重要的角色。通过利用智能技术和先进的收银系统&#xff0c;食堂能够实现快速、准确和便捷的收银过程&#xff0c;为顾客提供更好的用餐体验。 智慧食堂是适应现代社会对快捷、便利、个性化餐饮服务需求的创新解决方案&#xff…

面试题更新之-HTML5的新特性

文章目录 导文新特性有哪些&#xff1f;HTML5的新特性带来了许多好处 导文 面试题更新之-HTML5的新特性 新特性有哪些&#xff1f; HTML5引入了许多新特性和改进&#xff0c;以下是一些HTML5的新特性&#xff1a; 语义化标签&#xff1a;HTML5引入了一系列的语义化标签&#…

在vue中点击弹框给弹框中的表格绑值

场景描述&#xff1a;如下图所示&#xff0c;我们需要点击 ‘账单生成’ 按钮&#xff0c;然后里边要展示一个下图这样的表格。 最主要的是如何展示表格中的内容&#xff0c;一起看看吧&#xff01; <template><!-- 水费 欠费--><el-dialog title"水费欠费…

pnpm安装方式

pnpm安装方式 要使用pnpm进行安装&#xff0c;首先需要确保已经安装了Node.js。然后&#xff0c;按照以下步骤进行pnpm的安装&#xff1a; 打开终端或命令提示符。 在命令行中输入以下命令来全局安装pnpm&#xff1a; npm install -g pnpm这将使用npm将pnpm包全局安装到您的…