【C语言进阶】动态内存管理及柔性数组

动态内存的开辟在C语言中相当重要的知识


1、为什么会存在动态内存分配

内存的开辟方式:

int a=20;//在栈空间上开辟4个字节

int arr[10];//在栈空间上开辟40个字节的连续空间

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

1、开辟的空间大小是固定的

2、数组在声明的时候,必须指定数组长度,它需要的内存在编译时分配。

但是这种内存开辟的方式存在缺陷,比如我们在写通讯录管理系统时指定了100个元素,但当我们填入元素过多时空间会不够用,当联系人较少时,又会产生空间的浪费,所以我们可以用一种灵活的方式,用多少提供多少,这种方式即动态内存管理。

2、动态内存函数的介绍

2.1malloc和free

void * malloc(size_t  size);

· 如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。

· 返回类型为void*,所以malloc函数并不知道开辟空间的类型,具体在使用时侯使用者自己来决定。

· 如果参数size为0,malloc的行为是标准为定义的,取决于编译器。

free是专门用来做动态内存的释放和回收的:

void free(void* ptr)

· 如果参数ptr指向的空间不是动态开辟的,那么free的行为是未定义的

· 如果参数ptr是NULL指针,则函数什么事都不做

malloc函数与free函数的声明都在stdlib.h中

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
//malloc函数的使用
int main()
{int arr[10] = { 0 };//动态内存分配int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//说明开辟成功,使用内存int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d ", *(p + i));}//free(p);//p=NULL;return 0;
}
//没有free,并不是说内存空间就不回收了,当程序退出的时候,系统会自动回收内存空间的

注意:在使用malloc开辟空间时,使用完成一定要释放空间,否则可能导致内存泄漏。 

内存泄漏:是指程序动态分配内存后,未能及时释放这些内存,导致系统无法再为其他对象分配内存,或者可能导致系统内存耗尽的现象。

 2.2calloc

calloc函数也用来动态内存分配

void * calloc(size_t num,size_t size);

·​​​​​​​ 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0.

·​​​​​​​ 与函数malloc的区别在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){printf("%s\n", strerror(errno));return 1;}int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}//calloc与malloc的最大区别就是calloc会在返回地址之前把申请的空间的每个字节初始化为全0。

2.3realloc 

·​​​​​​​ realloc函数的出现让动态内存管理更加灵活

·​​​​​​​ 有时我们会发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,为了合理的分配内存,我们一定会对内存的大小做灵活地调整。那么realloc函数就可以做到对动态开辟内存大小的调整。

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

·​​​​​​​ ptr是要调整的内存地址

·​​​​​​​ size是调整后的新大小 

·​​​​​​​ 返回为调整之后的内存起始位置。

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

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

1、原有空间之后有足够大的空间;(直接追加)

2、原有空间之后没有足够大的空间;(会覆盖其他数据,所以开辟一块更大的空间,把原有数据拷贝过去)

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}int i = 0;for (i = 0; i < 10; i++){*(p + i) = i+1;}//扩容int* ptr = (int*)realloc(p, 80);//不能放在p中,因为若是给的数字过大,无法申请空间,从而返回空指针,会导致p原来的数据丢失{if (ptr != NULL)//扩容成功{p = ptr;}}for (i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p=NULL;return 0;
}

3、常见的动态内存错误

3.1对NULL指针解引用操作

int main()
{
    int* p = (int*)malloc(40);//应该判断p是否为空指针,否则空间可能开辟失败
    *p = 20;
    return 0;
}

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

int main()
{
    int* p = (int*)malloc(40);
    if (p == NULL)
    {
        return 1;
    }
    int i = 0;
    for (i = 0; i <= 10; i++)
    {
        p[i] = i;
    }
    free(p);
    p = NULL;
    return 0;
}

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

int main()
{
    int a = 10;
    int* p = &a;
    free(p);
    return 0;//这个代码会崩溃因为p所指向的空间是在栈区开辟的,并不是动态开辟的(堆区),不能进行释放
}

3.4使用free释放一块动态开辟内存的一部分

int main()
{
    int* p = (int*)malloc(40);
    if (p == NULL)
    {
        return 1;
    }
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        *p = i;
        p++;
    }
    free(p);
    p = NULL;//因为p的位置会发生改变不再是起始空间的地址,释放仅仅释放了一部分
    return 0;
}

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

int main()
{
    int* p = (int*)malloc(40);
    if (p == NULL)
    {
        return 1;
    }
    //.....
    free(p);//p一旦释放完后,所指向的空间已经回收,但p依然会记得地址,此时p就相当于一个野指针相当危险
    //.....
    free(p);
    return 0;
}

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

//情形一:

void test()
{
    int* p = (int*)malloc(40);
    //........
    int x = 0;
    scanf("%d", &x);//如果在这里用户输入1,是会直接返回,函数瞬间结束,malloc开辟的空间就永远无法释放,从而导致内存的泄露
    if (x == 1)
        return;
    //.......
    free(p);
    p = NULL;
}
int main()
{
    test();
    return 0;
}

//情形二:

int* test()
{
    int* p = (int*)malloc(40);
    if (p == NULL)
    {
        return p;
    }
    //.........
    return p;
}
int main()
{
    int* ptr = test();
    //忘记释放
    return 0;
}

4、典型例题分析

4.1题目1: 

void GetMeory(char* p)//形参p,里面放的也是NULL
{p = (char*)malloc(100);//p被赋值,使用malloc申请100个字节的空间,此时p不再是NULL,而是所申请空间的首元素(是个地址)
}//这个函数一旦结束,因为p是形参,只能在函数内部使用,出了函数,p就销毁了,但malloc申请的空间依然还在,从而发生空间泄露
void test(void)
{char* str = NULL;//str是局部变量GetMeory(str);//传递的是实参str,存放的是空指针strcpy(str, "hello world");//str此时依然为空指针,代码必然崩溃,因为strcpy模拟实现包括解引用操作,而对NULL解引用会出现错误printf(str);
}
int main()
{test();return 0;
}

 改写代码:

void GetMeory(char** p)
{*p = (char*)malloc(100);//对p解引用得到的其实就是str
}
void test(void)
{char* str = NULL;GetMeory(&str);//这个时候str里面存放的就是动态内存开辟的100个字节的空间strcpy(str, "hello world");printf(str);//释放free(str);str = NULL;
}
int main()
{test();return 0;
}

4.2题目2:

void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
int main()
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);return 0;
}//这个逻辑上是没有任何问题的,可以打印出hello,唯一的问题就是没有free

4.3题目3: 

void test(void)
{char* str = (char*)malloc(100);//申请了100个字节的空间放到strstrcpy(str, "hello");free(str);//把str指向的空间释放掉,但str并没有变,动态开辟的空间实际上已经还给操作系统了if (str != NULL){strcpy(str, "world");//这个时候str就已经是一个野指针了,形成非法访问printf(str);}
}
int main()
{test();return 0;
}

5、c/c++程序的内存开辟

 简单了解即可!

6、柔性数组

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

6.1柔性数组的特点:

· 结构中的柔性数组成员前面必须至少一个其他成员;

· sizeof返回的这种结构大小不包括柔性数组的内存;

· 包含柔性数组成员的结构用malloc( )函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

6.2柔性数组的使用:

//柔性数组的使用,如何访问空间
struct S
{int n;int arr[];
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S) + 40);//40是柔性数组想要开辟的字节大小,ps->n = 100;int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}for (i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}//柔性数组成员struct S* ptr=(struct S*)realloc(ps, sizeof(struct S) + 80);if(ptr!=NULL){ps=ptr;}//........free(ps);ps=NULL;//ptr已经赋给ps了所以不用释放ptr,释放平时即可return 0;
}
struct S
{int n;int* arr;
};
int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){return 1;}ps->n = 100;ps->arr = (int*)malloc(40);if (ps->arr == NULL){return 1;}//使用int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}for (i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}//释放free(ps->arr);ps->arr=NULL;free(ps);ps=NULL;return 0;
}

6.3柔性数组的优势 

 第一个好处:方便内存释放

如果我们的代码是在一个给别人使用的函数中,你在里面做了二次内存分配,并把整个结构体返回用户。用户调用free可以释放结构体,但是用户并不知道这个结构体,但是用户并不知道这个结构体内的成员也需要free,所以不能指望用户来发现,所以,如果我们把结构体的内存以及成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存给释放掉。

第二个好处:有利于访问速度

连续的内存有利于提高访问速度,也有益于减少内存碎片 

 

 

 

 

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

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

相关文章

二叉树创建和遍历

个人主页 &#xff1a;敲上瘾-CSDN博客二叉树介绍&#xff1a;二叉树(详解)-CSDN博客 目录 一、二叉树的创建 二、二叉树的遍历 1.前序遍历 2.中序遍历 3.后序遍历 4.层序遍历 三、相关计算 1.总节点个数计算 2.叶子节点个数计算 3.深度计算 一、二叉树的创建 关于…

如何在路由器上安装代理服务:详细教程

如何在路由器上安装代理服务&#xff1a;详细教程 步骤一&#xff1a;通过漏洞进入路由器系统开启Telnet服务使用Telnet登录路由器系统查看系统信息和CPU信息步骤二&#xff1a;交叉编译MIPS程序 Go对MIPS的支持 安装TFTP Server使用BusyBox tftp传输文件在路由器系统中下载编译…

❤机器学习正则化算法的总结。耗时10个小时完成。❤

❤纯 干 货~❤ 目录 纯干货 1、L1 正则化&#xff08;Lasso 正则化&#xff09; 2、L2 正则化&#xff08;岭正则化&#xff09; 3、弹性网络正则化&#xff08;Elastic Net 正则化&#xff09; 4、Dropout 正则化&#xff08;用于神经网络&#xff09; 5、贝叶斯Rid…

海外盲盒小程序:跨文化营销的利器

在全球化的浪潮下&#xff0c;跨境电商正迎来前所未有的发展机遇。作为这一领域中的新兴力量&#xff0c;海外盲盒小程序凭借其独特的魅力和优势&#xff0c;正逐渐崭露头角&#xff0c;成为跨文化营销的利器。本文将探讨海外盲盒小程序在跨文化营销中的应用及其带来的价值。 一…

【30天精通Prometheus:一站式监控实战指南】第16天:snmp_exporter从入门到实战:安装、配置详解与生产环境搭建指南,超详细

亲爱的读者们&#x1f44b;   欢迎加入【30天精通Prometheus】专栏&#xff01;&#x1f4da; 在这里&#xff0c;我们将探索Prometheus的强大功能&#xff0c;并将其应用于实际监控中。这个专栏都将为你提供宝贵的实战经验。&#x1f680;   Prometheus是云原生和DevOps的…

ldap协议(常用于统一身份认证)与dict协议(在线词典)

文章目录 LDAPDICT LDAP LDAP&#xff08;Light Directory Access Portocol&#xff09;&#xff0c;轻量目录访问协议。 目录是一个为查询、浏览和搜索而优化的数据库&#xff0c;它成树状结构组织数据&#xff0c;类似文件目录一样。 目录数据库和关系数据库不同&#xff0c…

Docker安装极简版(三分钟搞定)

什么是Docker? Docker是一个开源的应用容器引擎&#xff0c;它允许开发者打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口。 化。容器是…

简易图像处理器的设计

1 概述 Python是一种高级、通用、解释型的编程语言&#xff0c;由Guido van Rossum于1991年创造。它被设计为易读易写的语言&#xff0c;具有简洁而清晰的语法&#xff0c;使得它成为许多领域的首选语言&#xff0c;如Web开发、科学计算、人工智能、数据分析等。结合本科阶段以…

三维地图校内导航系统解决方案

在如今的数字化时代&#xff0c;越来越多的学校开始实施智慧校园计划&#xff0c;旨在为学生和教师提供更高效、便捷的学习和教学环境。智慧校园运用互联网、大数据、人工智能等技术&#xff0c;对校园内各信息进行收集、整合、分析和应用&#xff0c;实现教学、管理、服务等多…

【matlab】绘图插入并放大/缩小子图

参考链接 代码分为两个&#xff1a;绘图代码与magnify.m 绘图代码就是普通的绘图代码&#xff0c;以下为例 %https://zhuanlan.zhihu.com/p/655767542 clc clear close all x 0:pi/100:2*pi; y1 sin(x); plot(x,y1,r-o); hold on y2sin(x)-0.05; y3sin(x)0.05; xlim([0 2*…

eclipse-向Console控制台输出信息

首先这里主要用到的是org.eclipse.ui.console这个包&#xff0c;所以现在顺道先来了解一下&#xff1a; org.eclipse.ui.console是一个可扩展的console视图插件&#xff0c;利用它可以实现各种console&#xff0c;并把它们显示出来。该插件本身就实现了一个Message Console&…

本地 Java API 访问云上 HDFS 集群的问题与解决

前言 这篇文章默认是已经在云上配置好了 Haoop 集群&#xff0c;因此本文主要是记录一些可能会出现错误的地方。 如果还不会配置 Hadoop 集群&#xff0c;那么可以参考本专栏的另一篇文章&#xff1a;云上配置 Hadoop 集群详解 另外在进行本文的学习之前也建议先看看该文章&…

边缘计算的AI小板——OrangePi AI Pro

简介 OrangePi AI Pro是一款基于Allwinner H6处理器的嵌入式AI计算设备&#xff0c;适用于物联网和边缘计算。它具有强大的性能、低功耗、多接口和小尺寸。 本文分为三个部分&#xff1a; 一、对该板进行简单的开箱介绍。 二、 将SD卡中的系统迁移到由于该板支持SD卡、SSD…

必看——怎么让网站实现HTTPS访问?

让网站实现HTTPS访问的步骤可以简化为以下几个基本步骤&#xff0c;非常适合非技术背景人士理解&#xff1a; 1. 申请SSL证书&#xff1a; - SSL证书是实现HTTPS的关键&#xff0c;它能加密网站数据&#xff0c;保证用户信息的安全。你可以从一些提供免费SSL证书的机构&#xf…

Spring boot集成mybatis

Spring boot集成mybatis maven依赖 我的spring boot版本是2.5.0&#xff0c;集成mybatis&#xff0c;首先需要数据库的支持&#xff0c;这里我选择mysql数据库&#xff0c;版本是8.0.11&#xff0c;然后使用druid连接池&#xff0c;其次就需要加上mybatis的依赖。 <!--mys…

[ue5]建模场景学习笔记(2)——用vectornoise降低重复率

1.问题分析&#xff1a; 利用改uv的方式降低重复率并不理想&#xff0c;在一定程度上的确能够达到降低重复率的效果&#xff0c;但远看仍然有较清晰的重复效果&#xff0c;尝试优化一下。 2.操作实现&#xff1a; 1.首先先看一下修改后的效果&#xff1a; 这是未修改前&#…

【Web API DOM02】如何获取、操作DOM元素

一&#xff1a;获取DOM元素 1 根据CSS选择器获取 语法格式如下&#xff1a; &#xff08;1&#xff09;选中一个DOM元素 document.querySeletor(CSS选择器) <ul><li>1</li><li>2</li><li>3</li> </ul> document.querySel…

Github上一款开源、简洁、强大的任务管理工具:Condution

Condution 是一款开源任务管理工具&#xff0c;它以简洁易用、功能强大著称。它旨在为用户提供一个简单高效的平台&#xff0c;帮助他们管理日常任务、提高工作效率。 1. Condution 的诞生背景 现如今&#xff0c;市面上存在着许多任务管理软件&#xff0c;但它们往往价格昂贵…

如何不用口吐莲花,照样成为社交达人

一、教程描述 每个人的一生&#xff0c;70%的时候都在沟通&#xff0c;与老板沟通、与家人沟通、与朋友沟通、与陌生人沟通&#xff0c;等等&#xff0c;但是你真的会沟通么&#xff1f;不论是工作上跟上司、同事和客户间的沟通&#xff0c;还是生活中与家人、朋友、伴侣间的沟…

[ICPC2024 Xi‘an I] ICPC2024 邀请赛西安站(7/8/13)

心得 [ICPC2024 Xian I] ICPC2024 邀请赛西安站重现赛 - 比赛详情 - 洛谷 7表示赛时ac了7个&#xff0c;8表示含补题总共ac数&#xff0c;13表示题目总数 题目 M. Chained Lights 打表&#xff0c;发现只有k1是YES //#include <bits/stdc.h> #include<iostream&…