C语言既然可以自动为变量分配内存,为什么还要用动态分配内存呢?

news2025/7/22 11:15:04

一、前言

不知道大家在学习C语言动态分配内存的时候有没有过这样的疑问,既然系统可以自动帮我们分配内存,为什么还需要我们程序员自己去分配内存呢?
在这里插入图片描述

如果想要弄清楚这些问题,我们首先就要了解静态内存和动态内存有什么区别,只有了解了他们两个的区别我们才能弄懂(理解)为什么需要动态分配内存!

今天的文章会用到以下知识点,大家可以作为了解内容去学习:静态内存、动态内存、堆、栈、全局变量、指针等;

二、基础知识

既然要学习内存的相关知识,那我们就先从计算机的内存开始本篇的讲解吧!在计算机内存一共可以分为五个区域,其中有个区域是用来存储代码的,我们就不再进行讨论了。我们首先对这四个区域进行一个简单的了解,方便我们后面对于内存分配的理解。

我们首先看一张内存的组成图:
在这里插入图片描述
从上面的图我们可以看出内存区域大概可以分为五个部分:堆、栈、全局/静态存储区和常量存储区、文字常量区。下面我们对这几个名词进行一下简单的讲解,心里先有个概念。

栈: 栈又叫堆栈,该区域是由编译器自动分配自动回收的变量的存储区。通常是用来存储局部变量的值、函数参数值等,是向下增长的。所谓向下生长的就是,先调用的栈帧的地址比后调用的地址大,栈一般大小有几个M左右。

堆: 就是那些由程序员通过malloc函数申请到的内存块,一般我们申请的内存空间系统是不会帮我们释放的(当然有些也会由系统释放掉),由我们的应用程序去控制,一般一个malloc就要对应一个delete/free,由程序员主动释放。

全局区(静态区): 全局变量和静态变量都存储在这块区域,与其余变量的明显区别就是生命周期不一样,在程序结束时,系统会释放掉。

文字常量区 : 这个区域主要用来储存一些我们定义的常量,例如下面的定义就会被存储在文字常量区:char* p = "hello word!";。该部分也是由系统控制,程序结束后由系统释放掉。

代码区: 该区域主要用来存放程序代码,程序结束后由系统释放。

通过上面的基本概念我们已经知道了内存中的几个区域,以及哪些区域是我们程序员可以手动释放的,哪些区域是由系统为我们自动释放的。

我们今天主要需要用到的是,因为我们今天要讨论的动态内存和静态内存和堆栈是密切相关的。动态内存是指在堆上分配的内存,而静态内存是指在栈上分配的内存。 这里也给大家贴出一张网上的图片,便于大家理解上面的知识。

在这里插入图片描述

了解完堆栈之后我们还有个知识需要了解就是指针,由于我对于指针的理解还不是特别透彻,所以有哪些说的不对的地方大家可以在评论区指出来,我会即时进行修改。

明明我们今天要讨论的是动态内存和静态内存,为什么要了解指针呢?如果你有这样的疑问说明你对于内存或者指针的理解还不是特别到位。指针和内存的联系非常紧密,没有内存指针也将失去意义,我们对指针进行的操作实际上就是在间接的操作内存。但是大家需要注意指针也是有类型的,他的数据类型取决于它所指向的内存空间的数据类型。关于指针和内存的关系我们后面会进行详细的讲解。

三、为什么要使用动态内存

有了上面基础知识的加持,我们现在就可以回归我们今天的主题来讨论为什么我们需要动态内存了!我这里先说一下我的理解,我对这个问题的答案总结出以下几点,当然这绝不是全部的原因,鄙人也是能力有限,只能理解到这种程度,更多的理解欢迎大家在评论区进行讨论!

  1. 节省资源:用多少申请多少,不需要了及时进行释放,这样可以避免资源的浪费。
  2. 方便储存大型对象:大家需要注意栈区不是无限大的,对于大型项目如果说有的变量都储存在栈区,很可能会造成栈区内存不够用。
  3. 方便对象的调用 :对于较大的对象我们使用动态内存存储时我们只需要通过指针将变量首地址传递出去即可,而不用将整个对象都进行传递。

对于上面说的三点我可以给大家举个简单的例子,方便大家理解:

对于第一点大家应该很好理解,我用多少就申请多少,节省支援,但是后面两点可能就不是很好理解了,这里给大家举个简单的例子:

你是一个开超市的,栈区就相当与你的超市,但是你会发现如果你如果把商品都放到超市,可能你的超市会装不下那么多货物。于是仓库就出现了,栈区就相当于你的仓库。这些仓库和你的超市是分离的,如果你发现你进了一些商品,这些商品短时间内也不会被完全卖出去,那你就可以把这些货物放到你的仓库里,而你只需要记住你仓库的地址即可。

这样就可以保证你的超市不会因为堆积太多商品而显得拥挤,如果有人要买这些商品,你可以把仓库地址告诉他,他就会直接去你仓库拿货。

通完这个故事你可能更迷糊了,我下面给你梳理一下,相信你会豁然开朗!

动态申请空间,能动态确定对象所需要的内存。

我需要多大的空间,就用多大的仓库存放该商品。

对于大型对象的存储,栈区容不下。

我有大量的商品,都放超市太占地方。可以放仓库中,记住仓库地址就行。

传递指针比传递整个对象更高效。

别人要买该商品,告诉别人我仓库地址,不用把整个仓库搬过去。

(感觉这个故事我还是没有讲好,表达能力欠佳)

知道了动态分配内存的好处后我们就可以更好的理解我们为什么要使用动态分配内存以及何时应该使用动态分配了,所以如果你进了几包方便面(建了个很小的对象)那你就没必要把方便面放到仓库了,直接放到超市货架上就可以了。

如果你超市比较小(代码量比较小)那你也没必要把东西放到仓库了,直接放到柜台上就可以了。所以很多问出为什么要使用动态分配内存的主要原因是因为他现在还没接触过大型项目,或者特别大的对象,如果你做过底层驱动开发或者上位机开发的话相信你对于动态申请内存并不会陌生的。

四、什么时候需要动态分配内存

通过上面的故事我们大概也已经知道什么时候我们需要使用动态分配内存了,这里再简单的给大家做一个总结。

1、当你的代码量很大,需要用到很大的数据块来存储对象时。
2、当你的程序中用到大数组时,你就需要用动态分配内存。
3、需要数组长度根据程序进行变化。
4、想让一个变量储存的内容不会因为函数的结束而被收回(有点像全局变量)

这里就不得不来讨论一下“传统数组”的缺点了,传统数组”就是前面所使用的数组,与动态内存分配相比,传统数组主要有以下几个缺点:

  1. 数组的长度必须事先指定,而且只能是常量,不能是变量。比如像下面这么写就是对的:
int a[5];
而像下面这么写就是错的:
int length = 5;
int a[length];  //错误
  1. 因为数组长度只能是常量,所以它的长度不能在函数运行的过程当中动态地扩充和缩小。

  2. 对于数组所占内存空间程序员无法手动编程释放,只能在函数运行结束后由系统自动释放,所以在一个函数中定义的数组只能在该函数运行期间被其他函数使用。

而动态内存就不存在这个问题,因为动态内存是由程序员手动编程释的,所以想什么时候释放就什么时候释放。只要程序员不手动编程释放,就算函数运行结束,动态分配的内存空间也不会被释放,其他函数仍可继续使用它。除非是整个程序运行结束,这时系统为该程序分配的所有内存空间都会被释放。

所谓“传统数组”的问题,实际上就是静态内存的问题。我们讲传统数组的缺陷实际上就是以传统数组为例讲静态内存的缺陷。本质上讲的是以前所有的内存分配的缺陷。正因为它有这么多缺陷,所以动态内存就变得很重要。动态数组能很好地解决传统数组的这几个缺陷。

五、如何动态分配内存

知道了我们为什么要动态分配内存之后我们一起来学习以下C语言中如何进行动态分配内存。在C语言中动态分配内存使用的是函数malloc进行分配的。

malloc 是一个系统函数,它是 memory allocate 的缩写。其中memory内存的意思,allocate分配的意思。顾名思义 malloc 函数的功能就是分配内存。要调用它必须要包含头文件 <stdlib.h>,它的原型为:

# include <stdlib.h>
void *malloc(unsigned long size);

由上面的函数原型我们可以看出malloc 函数只需要一个形参,并且该形参是整形的。函数返回值为一个指向所分配的连续空间的首地址的指针。当函数未能成功分配存储空间时(如内存不足)则返回一个NULL指针。所以malloc 函数的返回值为一个指针。

由于堆区内存也是有限的,不能无限制地分配下去,所以秉持着尽量节省资源,我们应该在分配的内存区域不用时,及时释放它,以便其他的变量或程序使用。

释放malloc 函数分配内存的函数是free函数,free函数和malloc 总是成对出现的。free函数的原型如下所示:

# include <stdlib.h>
void free(void *p);

由上面的函数原型可以看出free函数需要一个形参,且形参的类型是一个指针。free 函数无返回值,它的功能是释放指针变量 p 所指向的内存单元。此时 p 所指向的那块内存单元将会被释放并还给操作系统,不再归它使用。操作系统可以重新将它分配给其他变量使用。

知道了申请和释放要用到哪些函数后我们来一起看一下我们该如何使用这些函数来申请和释放内存。

我们这里直接贴出malloc 函数动态分配内存的使用语句:

int *p = (int *)malloc(4);

它的意思是:请求系统分配 4 字节的内存空间,并返回第一字节的地址,然后赋给指针变量 p。当用 malloc 分配动态内存之后,上面这个指针变量 p 就被初始化了。

需要注意的是,函数 malloc 的返回值类型为 void* 型,而指针变量 p 的类型是 int* 型,即两个类型不一样,那么可以相互赋值吗?

答案是可以的,原因如下:上面语句是将 void* 型被强制类型转换 成 int*型,但事实上可以不用转换。C 语言中,void* 型可以不经转换(系统自动转换)地直接赋给任何类型的指针变量(函数指针变量除外)。

所以int*p = (int*)malloc(4);就可以写成 int*p=malloc(4);。此句执行完之后指针变量 p 就指向动态分配内存的首地址了。

我们知道如何申请一块内存了,也知道何时需要申请内存了,下面我们就来学习一下free函数的使用。

六、如何将动态分配内存free掉

在讲解之前有一点需要提醒一下大家,free函数只能释放堆区的空间,其他区域的空间无法使用free函数的。

下面给大家贴出一段动态申请内存的程序,来给大家讲解一下free的使用。

# include <stdio.h>
# include <stdlib.h>
int main(void)
{
    int *p = malloc(sizeof*p);
    *p = 10;
    printf("p = %p\n", p);
    free(p);
    printf("p = %p\n", p);
    return 0;
}

输出结果是:

p = 002C2ED0
p = 002C2ED0

上面的代码和结果可以看到释放前后,p 所指向的内存空间是一样的。所以释放后 p 所指向的仍然是那块内存空间。

既然指向的仍然是那块内存空间,那么就仍然可以往里面写数据。可是释放后该内存空间已经不属于它了,该内存空间可能会被分配给其他变量使用。如果其他变量在里面存放了值,而你现在用 p 往里面写入数据就会把那个值给覆盖,这样就会造成其他程序错误,所以当指针变量被释放后,要立刻把它的指向改为 NULL

那么,当指针变量被释放后,它所指向的内存空间中的数据会怎样呢?free 的标准行为只是表示这块内存可以被再分配,至于它里面的数据是否被清空并没有强制要求。

七、结语

对于动态分配内存今天就给大家介绍到这里,自己水平也是有限,文中可能存在表述不正确的地方,希望大家发现后及时在评论区指出,我会及时给大家修改。如果您觉得这篇文章对您有用,记得一键三连哦,谢谢!另外大家如果想要学习C语言、Linux、嵌入式的话可以关注一下下方的名片,后台回复 “资料”即可免费获取全部资料。

八、参考资料

  1. 郝斌C语言教程【年代虽已远,精华却不减】
  2. 动态内存分配,C语言动态内存分配详解
  3. 为什么要动态分配内存?什么时候需要动态分配内存?
  4. 一文读懂 Linux 动态内存分配机制
👇点击下方公众号卡片获取资料👇

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

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

相关文章

python学习笔记

第一个python程序 pycharm使用 pycharm下载链接&#xff1a; https://www.jetbrains.com/zh-cn/pycharm/download/#sectionwindows 1. 新建项目 右键test—> 点击new 输入文件名 运行 结果 2. 调节字体大小快捷键设置 右键添加鼠标快捷键 按住ctrl鼠标向上滑动 放…

计算机网络 应用层

概述 为特定应用程序提供数据服务&#xff0c;例如HTTP、FTP(File Transfer Protocol , 文件传输协议)、DNS &#xff08;Domain Name System,域名系统&#xff09;等。 域名系统 DNS DNS 全名叫 Domain Name Server&#xff0c;中文俗称“域名服务器” 在 Internet 上域名…

Docker搭建Nginx实现SpringBoot+Nginx集群

1&#xff0c;首先聊聊什么Nginx&#xff1f; Nginx 是一个高性能的HTTP和反向代理web服务器&#xff0c;因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。2011年6月1日&#xff0c;nginx 1.0.4发布。 Nginx是一款轻量级的Web 服务器/反向代理服务器及电…

linux三剑客awk、sed、grep与cut的总结

在Linux中&#xff0c;一切皆文件&#xff0c;对Linux的操作就是对文件的处理。对文件操作处理最重要的三个命令是grep、sed、awk&#xff0c;它们在业界被称为“三剑客”。 三剑客的功能非常强大&#xff0c;但它们各自有分别擅长的功能&#xff1a; grep擅长对文件或字符串进…

【操作系统】2.3 进程同步与互斥

这一节大概是操作系统中最难的一节了。 2.3.1 进程的同步与互斥 2.3.1 进程的同步与互斥_StudyWinter的博客-CSDN博客_进程同步思维导图 进程同步&#xff1a;在多道程序环境下&#xff0c;进程是并发执行的&#xff0c;不同进程之间存在着不同的相互制约关系。为了协调进程之…

C#界面里Form.Language 属性的使用

C#界面里Form.Language 属性的使用 现在面向全球化的应用软件、应用系统越来越多。 比如游戏正在走向全球化时代,很多游戏的服务器也会安装到各个国家去,也会请当地人来管理游戏的服务器。 这时开发的软件,就需要面向各种语言,比如英语、日语、阿拉佰语等等。 如果你正在…

什么叫共同富裕,刘强东给老板们打了个样

这两天&#xff0c;热搜上最火的男人&#xff0c;除了汪小菲&#xff0c;就是刘强东无疑了。 11月22日&#xff0c;刘强东发出了一封京东内部员工信&#xff0c;引发热议。 信里主要说了4点&#xff1a; 1、自明年1月1日起&#xff0c;为所有德邦员工缴齐五险一金&#xff1…

YOLOv5识别图像内苹果和香蕉

YOLOv5为目标检测带来了极大的方便。通过简单地训练YOLOv5&#xff0c;即可以实现一个速度快、性能高的目标检测系统。 下面介绍如何从头开始构造一个简单的目标检测系统&#xff0c;用来识别图像内的苹果和香蕉&#xff0c;并标注他们所在的位置。 特别强调的是&#xff0c;…

HTML5学习笔记(三)

离线应用 HTML5新增了一个离线存储的API&#xff0c;用于实现本地数据的缓存&#xff0c;从而使得开发离线应用成为可能。 所谓“离线存储”&#xff0c;指的是建立一个URL列表&#xff0c;该列表可以包含HTML文件、CSS文件、JavaScript文件和图片等。当与服务器建立连接时&am…

dragTabs(vue)

vue实现tabs拖拽 效果图 dragTab.vue <template><div class"yh-tabs"><draggable :list"tabList" :group"groupName" animation"300" item-key"id" end"dragEnd"><template #item"{ …

CANoe-vTESTstudio之Test Diagram编辑器(功能介绍)

1. 阶段 Test Diagram从测试设计到测试执行,分为5个阶段: Test Design 在测试设计阶段,测试设计人员使用图形元素和分配的测试代码对测试用例进行建模。这个阶段的结果是生成一个测试图表 Evaluation 生成的测试图表,需要评估其正确性。在评估期间,将验证各个元素及其…

3.7 学会这2招,让你的笔记分分钟上热门 【玩赚小红书】

什么是上热门&#xff1f; 上热门之前&#xff0c;我们先了解一下什么是上热门。上热门就是笔记被系统主动推荐&#xff0c;并在很长一段时间里&#xff0c;有源源不断的粉丝点赞、收藏&#xff0c;相当于平台给你的笔记开了专属流量口&#xff0c;推送给那些平常关注该笔记类型…

vue实战项目之vue-cli脚手架搭建过程详解

目录 1.可以参考vue-cli的中文文档进行下载安装 2.查看node和npm版本 3.安装依赖包 4.依赖包以前版本太老&#xff0c;如何更新&#xff1f; 5.创建一个项目 6.图形化界面 1.可以参考vue-cli的中文文档进行下载安装 vue-cli中文文档 2.查看node和npm版本 在cmd或者编译…

【数据库原理及应用】——关系数据库的规范化理论(学习笔记)

&#x1f4d6; 前言&#xff1a;关系数据库的规范化理论是数据库设计的一个理论指南&#xff0c;提供了判断一个关系模式优劣的理论依据。本章讨论的关系数据库的规范化理论主要包含三方面内容&#xff1a;函数依赖、范式和模式分解准则。函数依赖起着核心作用&#xff0c;是模…

易基因|TSD物种全基因组DNA甲基化模式对孵育性别和过去孵育温度的响应 | 性别决定

易基因&#xff5c;TSD物种全基因组DNA甲基化模式对孵育性别和过去孵育温度的响应 | 性别决定 大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 2022年8月23日&#xff0c;《MOLECULAR ECOLOGY》杂志发表题为“Genome-wide DNA methyla…

[附源码]java毕业设计幼儿园管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]Python计算机毕业设计二手书交易软件设计与实现

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

嵌入式驱动初级-字符设备驱动基础

文章目录前言一、驱动学习预备知识1.什么是设备驱动程序2.向内核添加新功能方法2.1新功能源码与Linux内核源码不在同目录下2.2在Ubuntu下加载和删除ko文件步骤2.3在开发板下加载和删除ko文件步骤2.4内核模块基础代码解析二、字符设备驱动框架2.1Linux内核对设备的分类2.2字符设…

css点击文字(非按钮) 能自动改变颜色。

实现功能说明&#xff1a; 如下图&#xff0c;点击不同的文字能够切换不同的页面&#xff0c;同时这个tab的文字能够相应的变色&#xff0c;其他未选中的状态默认为灰色。 方案一&#xff1a; 使用css的伪类。 :active&#xff0c;元素被点击时变色&#xff0c;但颜色在点击后…

为什么 NGINX 的 reload 命令不是热加载?

这段时间在 Reddit 看到一个讨论&#xff0c;为什么 NGINX 不支持热加载&#xff1f;乍看之下很反常识&#xff0c;作为世界第一大 Web 服务器&#xff0c;不支持热加载&#xff1f;难道大家都在使用的 nginx -s reload 命令都用错了&#xff1f;带着这个疑问&#xff0c;让我们…