内存管理问题

一、程序运行需要内存
所有的程序都是为了得到一定的结果而被运行的,计算机程序其实都是计算数据,所以数据是计算机程序的重要部分。
程序为什么需要内存呢?内存是用来存储可变数据的,数据在程序中表现为全局变量,局部变量和常量(也存储在内存中)对我们写程序非常重要,也对程序运行跟是本质相关。
所以内存对于写程序来说几乎是本质需求。越简单的程序需要越少的内存,而越庞大越复杂的程序需要更多的内存。内存管理是我们写程序时很重要的话题。
我们以前学过的和了解过的很多变成的关键都是为了内存,譬如数据结构(数据结构是研究数据如何组织的,数据是放在内存中的)和算法(算法是为了用更优秀更有效的方法来加工数据,既然跟数据有关就离不开内存)。

从操作系统角度讲:操作系统掌握所有的硬件内存,因为内存很大,所以操作系统把内存分成1个1个的页面(其实就是一块,一般是4KB),然后以页面为单位来管理。页面内用更细小的方式来以字节为单位管理。操作系统内存管理的原理非常麻烦、非常复杂、非常不人性化。那么对我们这些使用操作系统的人来说,其实不需要了解这些细节。操作系统给我们提供了内存管理的一些接口,我们只需要用API即可管理内存。譬如在C语言中使用malloc、 free这些接口来管理内存
没有操作系统时:在没有操作系统(其实就是裸机程序)中,程序需要直接操作内存,编程者需要自己计算内存的使用和安排。如果编程者不小心把内存用错了,错误结果需要自己承担。

从语言角度来讲:不同的语言提供了不同的操作内存的接口。

譬如汇编:根本没有任何内存管理,内存管理全靠程序员自己,汇编中操作内存时直接使用内存地址(譬如0xd0020010),非常麻烦;

譬如C语言:C语言中编译器帮我们管理直接内存地址,我们都是通过编译器提供的变量名等来访问内存的,操作系统下如果需要大块内存,可以通过API(malloc free)来访问系统内存。裸机程序中需要大块的内存需要自己来定义数组等来解决。

譬如C++语言:C++语言对内存的使用进一步封装。我们可以用new来创建对象(其实就是为对象分配内存),然后使用完了用delete来删除对象(其实就是释放内存)。所以C++语言对内存的管理比C要高级一些,容易一些。但是C++中内存的管理还是靠程序员自己来做。如果程序员new了一个对象,但是用完了忘记delete就会造成这个对象占用的内存不能释放,这就是内存泄漏。

Java/C#等语言:这些语言不直接操作内存,而是通过虚拟机来操作内存。这样虚拟机作为我们程序员的代理,来帮我们处理内存的释放工作。如果我的程序申请了内存,使用完成后忘记释放,则虚拟机会帮我释放掉这些内存。听起来似乎C# java等语言比C/C++有优势,但是其实他这个虚拟机回收内存是需要付出一定代价的,所以说语言没有好坏,只有适应不适应。当我们程序对性能非常在乎的时候(譬如操作系统内核)就会用C/C++语言;当我们对开发程序的速度非常在乎的时候,就会用Java/C#等语言。

二、内存编址、寻址

从硬件角度:内存实际上是电脑的一个配件(一般叫内存条)。
从逻辑角度:内存是这样一种东西:它可以随机访问(随机访问的意思是只要给一个地址,就可以访问这个内存地址)、并且可以读写(当然了逻辑上也可以限制其为只读或者只写);内存在编程中天然是用来存放变量的(就是因为有了内存,所以C语言才能定义变量,C语言中的一个变量实际就对应内存中的一个单元)。

从逻辑角度来讲,内存实际上是一个由无限多个内存单元格组成的,每个单元格有一个固定的地址叫内存地址,这个内存地址和这个内存单元格唯一对应且永久绑定。

以大楼来类比内存是最合适的。逻辑上的内存就好像是一栋无限大的大楼,内存的单元格就好像大楼中的一个个小房间。每个内存单元格的地址就好像每个小房间的房间号。内存中存储的内容就好像住在房间中的人一样。

三、C语言访问内存的方式

3.1 通过变量名访问内存

C语言对内存地址的封装(用变量名来访问内存、数据类型的含义、函数名的含义)
汇编在操作内存时是直接使用的内存的地址编号,如:ldr r0, = 0x11111111.
譬如在C语言中 int a; a = 5; a += 4; //a == 9
结合内存来解析C语言语句的本质:
int a; //编译器帮我们申请了1个int类型的内存格子(长度是4字节,地址是确定的,但是只有编译器知道,我们是不知道的,也不需要知道。),并且把符号a和这个格子绑定。
a = 5; //编译器发现我们要给a赋值,就会把这个值5丢到符号a绑定的那个内存格子中。
a += 4; //编译器发现我们要给a加值,a += 4 等效于 a = a + 4;编译器会先将a原来的值读出来,然后给这个值加4,再把加之后的和写入a所绑定的格子里去。
C语言中数据类型的本质含义是:表示一个内存格子的长度和解析方法。

3.2 用指针来间接访问内存

关于类型(不管是普通变量类型int,float等,还是指针类型int *,float *等),只要记住:类型只是对后面数字或者符号(代表的都是内存地址)所表征的内存的一种长度规定和解析方法规定而已。
C语言中的指针,全名叫指针变量,指针变量其实和普通变量没有任何区别,只是解析方法不同而已。譬如int a和int * p其实没有任何区别,a和p都代表一个内存地址(假如是0x20000000),但是这个内存地址(0x20000000)的长度和解析方法不同。a是int类型,所以a的长度是4字节,解析方法是按照int的规定来的(0x20000000开头的连续4个字节中存储的是一个int类型的数);p是int *类型,所以长度是4个字节,解析方法是按int *的规定来的(0x2000000开头的连续4个字节中存储了1个地址,这个地址所代表的内存单元中存放的是一个int类型的数)。

3.3 用数组来管理内存

数组管理内存和变量其实没有本质区别,只是符号的解析方法不同。(普通变量、数组、指针变量其实都没有本质差别,都是对内存地址的解析,只是解析方法不一样)。
int a; //编译器分配4字节长度给a,并且把首地址和符号a绑定起来。
int b[10]; //编译器分配40个字节长度给b,并且把首元素的首地址和符号b绑定起来。
数组中第一个元素(a[0])就称为首元素;每一个元素类型都是int,所以长度都是4,其中第一个字节的地址就称为首地址,首元素a[0]的首地址就称为首元素首地址。

四、栈和堆内存管理

4.1 内存管理之栈(stack)

栈是一种数据结构,C语言中使用栈来保存局部变量。栈是被发明出来管理内存的。

栈管理内存的特点(小内存、自动化)
先进后出(FILO),这个叫栈,特点是入口即出口,只有一个口,另一个口是堵死的,所以先进去的必须后出来。(弹夹
先进先出(FIFO),这个叫队列,入口和出口都有,必须从入口进去,从出口出来,所以先进去的必须先出来,否则就堵住后面的。
C语言中的局部变量是用栈来实现的。
我们在C中定义一个局部变量时(int a),编译器会在栈中分配一段空间(4字节)给这个局部变量用(分配时栈顶指针会移动给出空间,给局部变量a用的意思就是,将这4字节的栈内存的内存地址和我们定义的局部变量a给关联起来),对应栈的操作是入栈。
栈的优点:栈管理内存,好处是方便,分配和最后回收都不用程序员操心,C语言自动完成

4.2 内存管理之堆

堆(heap)是另外一种内存管理方式。内存管理对操作系统来说是一件非常复杂的事情,因为首先内存容量很大,其次内存需求在时间和大小块上没有规律(操作系统上运行着的几十、几百、几千个进程随时都会申请或者释放内存,申请或者释放的内存块大小随意)。
堆这种内存管理方式特点就是自由(随时申请、释放、大小快随意)。堆内存是操作系统划归给堆管理器(操作系统中的一段代码,属于操作系统的内存管理单元)来管理的,然后向使用者(用户进程)提供API(malloc和free等)来使用堆内存

我们什么时候使用堆内存?需要内存容量比较大时,需要反复使用及释放时,很多数据结构(譬如链表)的实现都要使用堆内存。
特点一:容量不限(常规使用的需求容量都能满足)。
特点二:申请及释放都需要手工进行,手工进行的含义就是需要程序员写代码明确进行申请malloc及释放free。如果程序员申请内存并使用后未释放,这段内存就丢失了(在堆管理器的记录中,这段内存仍然属于你这个进程,但是进程自己又以为这段内存已经不用了,再用的时候又会去申请新的内存块,这就叫吃内存),称为内存泄露。在C/C++语言中,内存泄露是最严重的程序bug,这也是别人认为Java/C#等语言比C/C++优秀的地方。

五、C程序的内存五区
全局区 :主要存储全局变量和static变量。

常量区:存放常量的地方。(也可把常量区和全局数据区归成一个区

堆区:用户申请的内存区,用编译器提供的函数new或malloc申请。要注意内存泄露的问题

栈区:系统自动给局部变量分配的内存区。

代码区:存放函数体的二进制代码

extern关键词:extern修饰全局变量glovar时,表明glovar可以被其他模块的函数使用;
extern修饰函数是,表明函数可以被其他模块的函数调用;
static关键词:static修饰局部变量var时,表明var在函数调用结束时不销毁,函数在此被调用时,不对VAR 再次定义,使用上次调用结束时的值,他的生命期:从第一次调用开始,到main结束;
static修饰全局变量glovar时,表明glovar不能够被其他模块的函数使用;
static修饰函数是,表明函数不能够被其他模块的函数调用;
voltile:表明变量可能在另外的进程修改,因此每次使用时,必须重新从内存读取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值