内存大话题
1、冯诺依曼结构和哈佛结构的区别
冯诺依曼结构和哈佛结构是计算机的两种不同的架构,他们的本质区别是对于数据和代码的存储是不同的。
冯诺依曼结构的是将数据和代码存储在一起,数据和代码都在RAM中。S5PV210就是这样的结构,还有Intel的CPU。程序和数据放在一起,处理简单,但是程序就变得可读可写了,因此安全和稳定性就是一个问题。
哈佛结构的将数据和代码分开存放,数据在RAM中,可随机访问,代码存放在ROM中,大部分单片机就是这样的结构,代码烧写到Flash中直接执行,代码运行中用到的数据从RAM中读取。程序、数据分开安全稳定,软件处理就需要统一规划链接地址等问题。
2、动态内存DRAM和静态内存SRAM
3、为什么需要内存
内存是用来存储可变数据的,数据在程序中表现为全局变量、局部变量等
内存对程序来说几乎是本质需求,在gcc交叉编译中,常量也是存储在内存中的;大部分单片机中,常量是存储在flash中的,也就是在代码段,内存对程序运行更是本质相关的。越简单的程序需要越少的内存,而越庞大越复杂的程序需要更多的内存。对内存的管理也是极为重要的。
数据结构:研究数据在内存中是如何组织的
算法:更有效的加工数据的方法,也离不开内存
4、为什么要管理内存
计算机中有大量的内存,这些内存的使用需要统一、规范的管理才能让程序安全稳定的运行。程序在运行过程中是会消耗内存的,如果管理不善,可能会造成程序运行中消耗过多的内存,这样迟早内存都会被这个程序吃光了,当没有内存可用时程序就会崩溃。
5、如何管理内存(操作系统和语言的角度)
(1)从操作系统角度讲:
有操作系统:
操作系统掌握了所有的硬件内存,操作系统把内存划分成了一个个的页面,一般为4Kb,然后以页面为单位来管理。操作系统给我们提供了内存管理的一些接口,我们只需要调用这些API接口即可管理内存(C语言中使用malloc、free等)
无操作系统:
也就是逻辑程序中,程序需要直接操作内存,我们需要自己计算和安排内存的使用。
(2)从语言角度来讲:
不同的语言提供不同的操作内存的接口
汇编:
没有任何内存管理,操作内存时直接使用内存地址
C语言:
编译器帮我们直接管理内存地址,通过编译器提供的变量名访问内存。操作系统下当需要大块内存时,通过提供的API接口访问内存;无操作系统时,通过定义数组来获取内存。
C++语言:
对内存的进一步封装,可以用new创建对象(分配内存),用delete来删除对象(释放内存)。C++的内存管理还是靠程序员自己去做的。
Java/C#等:
不直接操作内存,通过虚拟机操作内存。
总结:当程序对性能要求非常高时,就会用C/C++;当要求程序开发速度时,就采用java/C#等语言。
6、什么是内存泄漏
占用的内存没有及时的释放
7、什么是内存(从硬件和逻辑角度)
(1)硬件角度:实际的配件(内存条) SRAM(不需要初始化) DRAM(需要初始化) DRAM最早由SDRAM,后来有DDR1、DDR2…
(2)逻辑角度:内存就是一个可以随机访问,并且可以读写的一片区域,只要给一个地址就可以访问。内存天然是用来存放变量的(因为有了内存,程序才能定义变量,一个变量就是这片区域中的内容)
8、内存的逻辑抽象图(内存的编程单元)
内存----无限多个内存单元格------每个单元格就有一个固定的内存地址
内存----一栋无限大大楼------每一层就是一个内存地址-------每一层中的小房间大小就是内存的长度
内存中的内容----房间里的人
现实当中的内存是有限制的,它与CPU的地址总线是有关系的,如果地址总线是32位的,那么逻辑上内存就有232(4G)。16位的地址总线,逻辑上内存就是216(64Byte);16位的地址总线,逻辑上内存就是2^8(256bit);
32位地址总线可以访问的最大内存数是232个字节,访问内存是需要内存地址的,32位系统最多可以访问的地址个数就是232个,因为内存编址是以字节为单位的,所以就是4G的访问范围。
9、内存单元的单位
位:bit
字节:1Byte = 8bit
半字:16bit
字:一般是32bit
不同平台对于字和半字的定义是不同的,半字一定是字的一半,双字肯定是字的二倍,编程中字的概念是用不到的,文档中会提到。在linux+arm平台上字是32bit
10、内存位宽(硬件和逻辑角度)
(1)硬件:内存的实现本身是有宽度的,内存条有8位、16位…32位,内存芯片之间是可以并联的,两个16位并联就是32位
(2)逻辑:内存位宽是任意的,可以有24位,但是实际当中操作和硬件是有关联的,很多是受限于硬件特性的。
11、内存的编址方法
逻辑:内存—>一个个的小格子—>格子是用来装东西的—>给每个格子编号—>编号就是内存地址—>编号是永久的一一对应的
程序运行时,计算机中CPU实际只认识内存地址,不关心地址所代表的空间在哪里,怎么分布。
两个概念:地址(内存向外展示的编号、程序寻找的编号)、空间(内存真正的空间)
12、关键问题:内存编址是以字节为单位的
一个内存地址—>一个空间—>大小1字节(8bit)
一个内存格子,这个格子的大小是固定的8bit
13、内存与数据类型的关系
int整形,整就体现在它和CPU本身的数据位宽(数据总线)是一样的,32位CPU整形就是32位
数据类型是用来定义变量的,变量存储在内存中,数据类型就是提醒内存这个变量应该占用多少空间,数据类型于内存相匹配能够获得最好的性能,32位系统中最好用int,效率高,一次处理32位的数据
bool类型的特殊性,实际定义bool类型变量只需要1bit,但在32位环境下,都用int来实现bool,定义bool b1;时,编译器实际帮我们分配了32位的空间来存储这个bool类型的变量。效率高,但浪费了空间。
多年前以省内存为重,随着半导体的发展,内存便宜,现在以效率和用户体验为重
14、C语言对内存地址的封装
(1)用变量名来访问内存
int a; // 申请长度为4字节的内存空间,a与该空间关联
a = 6; // 把6放进申请的内存空间中
a += 4; // 把a原来的6读出来然后加4,再把加过之后的值写进a的内存空间
(2)数据类型的含义
本质:一个内存格子的长度和解析方法
决定长度:一个内存地址只代表1个字节的长度,给某一个地址数一个类型int,它就有了长度,长度为4,这样这个地址数开头的连续4个地址空间都用这个地址数来代表了。这个地址数也就是int定义的变量就有了4字节的长度。内存单元格子的编址单位是字节
(int *)0; // 0是一个内存地址,这个地址里存储的是指向int的指针,0地址向后4字节的内存空间
(float *)0; // 0是一个内存地址,这个地址里存储的是指向float的指针,0地址向后4字节的内存空间
(short)0; // 0是一个内存地址,这个地址里存储的是short类型的数据,0地址向后2字节的内存空间内存长度
(char)0; // 0是一个内存地址,这个地址里存储的是char类型的数据,0地址向后1字节的内存空间内存长度
决定解析方法:向内存空间里存储数据,就是将一组二进制数存放进去,通过数据类型就可以指定这些二进制数如何解析。譬如,4字节内存空间中存储的二进制数是一模一样的,int和float不同的数据类型对于这一组数据的解析是不同的,int是把二进制数转换成十进制转换,float是把二进制数用科学计数法转换。
(3)函数名的含义
函数就是一段代码的封装,函数名就是这一段代码的首地址,找到这个首地址也就找到了这个函数。所以说函数名的本质也是一个内存地址。
15、指针如何来间接访问内存
指针是一种数据类型,数据类型只是对后面数字或符号(代表的是内存地址)所表征的内存的一种长度规定和解析方法的规定而已。C语言中的指针变量和普通的变量没有区别。比如int a int * p a、p都是内存地址,长度均为4字节,解析方法,a按照int的规定,p按照int*的规定(p代表的内存地址开头连续的4字节存储了1个地址,这个地址代表的内存单元中存放的是一个int型的数)
16-1、内存管理之数组
普通变量、数组、指针其实都没有本质差别,都是对内存地址的解析,只是符号和解析方法不同。
int a; // 编译器分配4字节内存空间给a,a和首地址关联
int b[10]; // 编译器分配4*10字节内存空间,首元素首地址和b关联
&b[0]就代表首元素首地址
数组是最简单的数据结构
多个类型相同、意义相关的变量的管理
优点:结构简单、可随机访问、访问下标
缺点:所有元素必须相同、大小必须给出,一旦确定不能增加元素
16-2、内存管理之结构体
解决数组的第一个缺陷----元素必须相同。在包中元素类型不同时就必须用结构体。
结构体内嵌指针(函数指针)实现面向对象:
用面向过程的语言实现面向对象的代码
struct s // 结构体 // class类
{
int age; // 普通变量 // 成员变量
void (*pFunc)(void); // 函数指针,指向void Func(void);这类函数 // 成员方法
};
16-3、内存管理之栈
-
栈(stack):一种数据结构
-
栈管理内存的特点:
先进后出(FILO)、小内存、自动化、两个栈指针(随时动的top指针、不动的bottom指针) -
栈的优点: 自动化的管理内存,它能够自动的分配和回收内存,不需要程序员操心
-
栈的缺点: 预定栈的大小不灵活、怕溢出(栈内存是事先规定好的,是内存中的一部分区域)
太小怕溢出,太大怕浪费内存
栈溢出危害极大,C语言中定义局部变量时不能定义太多或者太大(譬如不能定义局部变量int a[10000];使用递归来解决问题时一定要注意) -
C语言中的局部变量是用栈来实现的:
定义局部变量 int a; // 入栈:栈顶指针移动4字节的空间给a用(4字节地址与a关联)
栈指针的移动和内存分配是自动完成的
函数退出,局部变量就要消亡 // 出栈:对应的栈指针就要移动,将与a关联的4字节内存释放掉,自动,无需干扰。
定义的局部变量未初始化时,局部变量的值是随机的:局部变量是定义在栈上的,栈内存在反复的使用,上次使用完没有清零,所以未初始化的局部变量是随机的,因此使用栈实现的局部变量定义时需要显式初始化。
16-4、内存管理之堆
-
堆(heap):内存管理的方式
-
堆管理内存的特点:
大块内存(大小随意)、需要手工申请、使用和释放
操作系统中使用堆管理器(在内存管理单元中),用户进程使用操作系统提供的API接口来使用堆内存
申请和释放都需要手工来进行(代码明确申请malloc及释放free内存)
-
堆的优点:自由,可随时申请、释放,大小随意
-
对的缺点:需要程序员去处理细节,容易出错
-
C语言操作堆内存的接口:
-
free释放内存:void free(void * ptr);
-
malloc、calloc、realloc申请内存:
void *malloc(size_t size); void *calloc(size_t nmemb, size_t size); // nmemb * size个字节 void *realloc(void *ptr, size_t size); // 改变原来申请的空间大小 // 举例:申请10个int的内存空间 malloc(40); malloc(10 * sizeof(int)); calloc(10, 4); calloc(10, sizeof(int)) // 备注: 堆内存在申请时,必须给定大小,一旦申请完成大小不变,如果需要改变只能通过realloc接口。
-
void *calloc(size_t nmemb, size_t size);
-
手工进行内存申请、释放带来的问题:
- 内存泄漏:程序员申请了内存,但使用完成后没有释放掉那部分的内存,这段内存就丢失了
- 吃内存:在堆管理器的记录中,未释放的内存仍然属于这个进程,但是进程自己又以为这段内存已经不能用了,再用的时候就回去申请新的内存块,就造成了不断的消耗内存。
-
操作系统使用堆管理器管理内存:
- 内存管理对操作系统来说是非常复杂的,内存的容量很大
- 内存需求在时间和大小块上没有规律(操作系统上运行着成百上千个进程,他们随时会申请和释放内存,且大小随意)
- 需要操作系统有一个专门管理堆内存的单元,堆管理器