内存
第一个问题:什么是内存?
答:硬件上:电脑的一个配件(通常称之为内存条),根据不同的实现原理又分为SRAM,DRAM(SDRAM、DDR1、DDR2….LPDDR)。
逻辑上:它可以随机访问和读写,在编程中它天然是用来存放变量的。
SRAM(静态内存):静态随机存取存储器。谨记:SRAM不需初始化,上电即可使用。贵
DRAM(动态内存):动态随机存取存储器,最为常见的系统内存。谨记:DRAM上电不能直接使用,需要先初始化。便宜。
共同点:断电后都会丢失数据。
什么是外存?
答:外储存器是指除计算机内存及CPU缓存以外的储存器,此类储存器一般断电后仍然能保存数据常见的外存储器有硬盘、软盘、光盘、U盘、flash等。在s5pv210中:采用小容量的外存Norflash(iRom)+小容量静态内存SRAM(iRAM)+大容量内存DRAM+大容量外存Nandflash(iNand)
第二个问题:为什么需要内存?
答:计算机程序=代码+数据。 代码:函数 数据:变量(普通变量+全局变量) 数据经过代码运行后产生一个结果。
代码和数据不可能凭空出现供你使用,那就涉及到一个问题:代码放在哪儿?数据又放在哪儿?
先看两种结构: 冯诺依曼结构:代码和数据放在一起。 哈佛结构:代码和数据分开存放。
举例:(1)s5pv210运行在linux系统上,运行应用程序时,所有数据和代码都放在DRAM中,这就叫做冯诺依曼结构。
(2)单片机中,我们把程序代码烧写到Flash(NorFlash)中,然后程序在Flash中原地运行,程序中所涉及到的
数据(全局变量、局部变量)放在 RAM(SRAM)中,这就是哈佛结构。
无论是上述哪种结构运行程序都必须用到内存存放数据,因此内存对于程序来说是必须的。
第三个问题:如何管理内存?
答:1、从操作系统角度:
(1)有操作系统时:操作系统将内存划分为一个个4KB的页面,再在页面的基础上进一步划分为以字节为单位。操作系统给我们提供了内存管理的一些接口,我们只需要用API即可管理内存。譬如C语言中我们使用malloc free这些接口来管理内存。
(2)无操作系统时:程序需要直接操作内存,编程者需要自己计算内存的使用和安排。如果编程者不小心把内存用错了,错误结果自己承担。
2、从语言角度:
(1)譬如汇编:没有任何内存管理,内存管理全靠程序员自己,汇编中操作内存时直接使用内存地址(譬如0xd0020010);
(2)C语言:编译器帮我们管理内存地址,我们可通过编译器提供的变量名等来访问内存,操作系统下如果需要大块内存,可以通过API(malloc free)来访问系统内存。裸机程序中需要大块的内存需要自己来定义数组等来解决。
(3)譬如C++语言:C++中,我们可以用new来创建对象(为对象分配内存),然后使用完了用delete来删除对象(释放内存)。所以C++语言对内存的管理比C要高级一些,容易一些。但是C++中内存的管理还是靠程序员自己来做。如果程序员new了一个对象,但是用完了忘记delete就会造成这个对象占用的内存不能释放,这就是内存泄漏。
(4)Java/C#等语言:这些语言不直接操作内存,而是通过虚拟机来操作内存。这样虚拟机作为我们程序员的代理,来帮我们处理内存的释放工作。虚拟机会帮我释放掉那些忘记释放的内存。但是虚拟机回收内存是需要付出一定代价的,所以说语言没有好坏,只有适应不适应。当我们程序对性能非常在乎的时候(譬如操作系统内核)就会用C/C++语言;当我们对开发程序的速度非常在乎的时候,就会用Java/C#等语言。
零散知识
内存位宽:内存位宽是在一个时钟周期内所能传送数据的位数。譬如32位的s5pv210为32位CPU,它的内存位宽为32位。
内存的编址方法:内存在逻辑上就是一个个格子,每一个格子的编号即内存地址,该内存地址和格子永久绑定。每个格子占1个字节,故相邻内存地址编号相差1;
内存匹配:由于32位系统本身配合的内存也是32位的,因此定义一个int效率最高。所以很多32位环境下即使定义1bit的bool类型变量也是用int来实现的,即定
义一个bool b1;时,编译器实际帮我们分配了32位的内存来存储这个bool变量b1。
内存对齐:我们在C中int a;定义一个int类型变量,在内存中就必须分配4个字节来存储这个a。有这么2种不同内存分配思路和策略:
第一种:0 1 2 3 对齐访问
第二种:1 2 3 4 或者 2 3 4 5 或者 3 4 5 6 非对齐访问
对其访问效率要比非对齐访问效率高很多
C语言中,int整形的整字体现在:位数==CPU的数据位宽。譬如32位CPU则int型就是32位的。
指针与变量在内存中的实际表现
#include<stdio.h> int main(void) { int a = 1; //这行代码实际执行时,编译器自动帮我们分配一个4字节的内存,并把首地址和变量名a绑定,以int类型的方式将数字1存在这四个字节的内存中 int *P; // 申请一个int *类型的变量p,随机分配四个字节内存,并把首地址和变量P的绑定。变量p为指针变量,他里面存的必须是一个地址。 p = &a; // 将变量a的地址放在变量p对应的4字节的内存空间中。注意&a是int *类型的。 *p = 4; //指针变量p所指向的那个值改为4,这样就实现了间接访问变量a printf("a=%d.\n", a); //将变量a的值打印出来。 return 0; }
内存管理之数组、结构体
当我们需要在程序中同时定义许多个类型相同、意义相关的数据的时候,定义数组就会很方便。
例如:int a[10]; //同时定义了10个int型变量,a[0]、a[1]、……a[9]不仅定义起来方便,而且在会内存中连续排布,操作也简单。
但是数组本身也有自身的局限性:
第一,数组内的变量的数据类型必须完全一致。
第二,数组的大小必须定义时给出,且定义后就不可变。
为了解决数组的第一个缺陷,我们引出结构体的概念:
struct student { char name; int num; int ages; }; struct student s1;
上述的例子中,在结构体变量student中,有char型、int型两种不同类型的变量。
局部变量:在子程序中定义的变量称为局部变量,局部变量的作用域为定义该变量的子程序。
全局变量:在程序的一开始定义的变量称为全局变量。作用域为整个程序。
虽然,全局变量常常比局部变量的调用效率更高(局部变量往往需要传参)。但是为了增加程序的易读性,应尽量少定义全局变量,更多的使用局部变量。为了方便的管理局部变量,我们可以用栈的方式来管理。
内存管理之栈
栈管理内存的特点:先进先出(FILO),栈的优点:栈管理内存,好处是方便,分配和最后回收都不用程序员操心,C语言自动完成。又由于栈是反复被使用的,所以如果定义局部变量时,没有显式初始化,值是脏的。
栈的大小是有约束的,定义局部变量是一定要注意栈溢出的问题。譬如:不能定义局部变量时 int a[10000]; 使用递归来解决问题时一定要注意递归收敛
内存管理之堆
有时候我们需要申请一大块内存,需要反复使用及释放则可以使用堆内存。特点:容量不限,申请(malloc)和释放(free)需要手工进行。
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));
复杂数据结构
链表、哈希表、二叉树、图等
总结:
因为程序中的数据需要存在内存中,所以需要内存。在有操作系统和无操作系统是内存管理是不一样的。不同的语言之间内存管理方式也显著不同。在C语言中,常见的内存管理有数组、结构体、栈、堆、链表、哈希表、二叉树、图等等。对于一些零零散散的概念需要用心记住。
(注:本文所在的C语言这个文件夹中各个部分内容都参考了朱有鹏老师的C语言高级专题精讲)