垫江网站建设费用,海外网站建设平台,一般纳税人企业所得税怎么征收,中石化第五建设有限公司官方网站1、程序为什么需要内存
1.1、计算机程序运行的目的 计算机为什么需要编程#xff1f;编程已经编了很多年#xff0c;已经写了很多程序#xff0c;为什么还需要另外写程序#xff1f;计算机有这个新的程序到底为了什么#xff1f; 程序的目的是为了去运行#xff0c;程序…1、程序为什么需要内存
1.1、计算机程序运行的目的 计算机为什么需要编程编程已经编了很多年已经写了很多程序为什么还需要另外写程序计算机有这个新的程序到底为了什么 程序的目的是为了去运行程序运行是为了得到一定的结果。计算机就是用来计算的所有的计算机程序其实都是在做计算。计算就是在计算数据。所以计算机程序中很重要的部分就是数据。 计算机程序 代码 数据计算机程序运行完得到一个结果就是说代码 数据 (经过运行后) 结果 从宏观上来理解代码就是动作就是加工数据的动作数据就是数字就是被代码所加工的东西。 那么可以得出结论程序运行的目的不外乎2个结果、过程 用函数来类比函数的形参就是待加工的数据函数内还需要一些临时数据就是局部变量函数本体就是代码函数的返回值就是结果函数体的执行过程就是过程。
int add(int a, int b){return a b;} // 这个函数的执行就是为了得到结果
void add(int a, int b){int c;c a b;printf(c %d.\n, c);} // 这个函数的执行重在过程重在过程中的printf返回值不需要
int add(int a, int b){int c;c a b;printf(c %d.\n, c);return c;} // 这个函数又重结果又重过程
1.2、计算机程序运行过程 计算机程序的运行过程其实就是程序中很多个函数相继运行的过程。程序是由很多个函数组成的程序的本质就是函数函数的本质是加工数据的动作。
1.3、冯诺依曼结构和哈佛结构 冯诺依曼结构是数据和代码放在一起。 哈佛结构是数据和代码分开存在。 什么是代码函数 什么是数据全局变量、局部变量 在S5PV210中运行的linux系统上运行应用程序时这时候所有的应用程序的代码和数据都在DRAM所以这种结构就是冯诺依曼结构在单片机中我们把程序代码烧写到FlashNorFlash中然后程序在Flash中原地运行程序中所涉及到的数据全局变量、局部变量不能放在Flash中必须放在RAMSRAM中。这种就叫哈佛结构。
1.4、动态内存DRAM和静态内存SRAM DRAM是动态内存SRAM是静态内存。
1.5、总结 内存是用来存储可变数据的数据在程序中表现为全局变量、局部变量等在gcc中其实常量也是存储在内存中的大部分单片机中常量是存储在flash中的也就是在代码段对我们写程序来说非常重要对程序运行更是本质相关。 所以内存对程序来说几乎是本质需求。越简单的程序需要越少的内存而越庞大越复杂的程序需要更多的内存。内存管理是我们写程序时很重要的话题。我们以前学过的了解过的很多编程的关键其实都是为了内存例如说数据结构数据结构是研究数据如何组织的数据是放在内存中的和算法算法是为了用更优秀更有效的方法来加工数据既然跟数据有关就离不开内存。
1.6、思考如何管理内存无OS时有OS时 对于计算机来说内存容量越大则可能性越大所以大家都希望自己的电脑内存更大。我们写程序时如何管理内存就成了很大的问题。如果管理不善可能会造成程序运行消耗过多的内存这样迟早内存都被你这个程序吃光了当没有内存可用时程序就会崩溃。所以内存对程序来说是一种资源所以管理内存对程序来说是一个重要技术和话题。 先从操作系统角度讲操作系统掌握所有的硬件内存因为内存很大所以操作系统把内存分成1个1个的页面其实就是一块一般是4KB然后以页面为单位来管理。页面内用更细小的方式来以字节为单位管理。操作系统内存管理的原理非常麻烦、非常复杂、非常不人性化。那么对我们这些使用操作系统的人来说其实不需要了解这些细节。操作系统给我们提供了内存管理的一些接口我们只需要用API即可管理内存。 例如在C语言中使用malloc free这些接口来管理内存。 没有操作系统时在没有操作系统其实就是裸机程序中程序需要直接操作内存编程者需要自己计算内存的使用和安排。如果编程者不小心把内存用错了错误结果需要自己承担。 再从语言角度来讲不同的语言提供了不同的操作内存的接口。 例如汇编根本没有任何内存管理内存管理全靠程序员自己汇编中操作内存时直接使用内存地址例如0xd0020010非常麻烦 例如C语言C语言中编译器帮我们管理直接内存地址我们都是通过编译器提供的变量名等来访问内存的操作系统下如果需要大块内存可以通过APImalloc free来访问系统内存。裸机程序中需要大块的内存需要自己来定义数组等来解决。 例如C语言C语言对内存的使用进一步封装。我们可以用new来创建对象其实就是为对象分配内存然后使用完了用delete来删除对象其实就是释放内存。所以C语言对内存的管理比C要高级一些容易一些。但是C中内存的管理还是靠程序员自己来做。如果程序员new了一个对象但是用完了忘记delete就会造成这个对象占用的内存不能释放这就是内存泄漏。 Java/C#等语言这些语言不直接操作内存而是通过虚拟机来操作内存。这样虚拟机作为我们程序员的代理来帮我们处理内存的释放工作。如果我的程序申请了内存使用完成后忘记释放则虚拟机会帮我释放掉这些内存。听起来似乎C# java等语言比C/C有优势但是其实他这个虚拟机回收内存是需要付出一定代价的所以说语言没有好坏只有适应不适应。当我们程序对性能非常在乎的时候例如操作系统内核就会用C/C语言当我们对开发程序的速度非常在乎的时候就会用Java/C#等语言。
2、位、字节、半字、字的概念和内存位宽
2.1、什么是内存硬件和逻辑两个角度 从硬件角度内存实际上是电脑的一个配件一般叫内存条。根据不同的硬件实现原理还可以把内存分成SRAM和DRAMDRAM又有好多代例如最早的SDRAM后来的DDR1、DDR2·····、LPDDR 从逻辑角度内存是这样一种东西它可以随机访问随机访问的意思是只要给一个地址就可以访问这个内存地址、并且可以读写当然了逻辑上也可以限制其为只读或者只写内存在编程中天然是用来存放变量的就是因为有了内存所以C语言才能定义变量C语言中的一个变量实际就对应内存中的一个单元。
2.2、内存的逻辑抽象图内存的编程模型 从逻辑角度来讲内存实际上是由无限多个内存单元格组成的每个单元格有一个固定的地址叫内存地址这个内存地址和这个内存单元格唯一对应且永久绑定。 以大楼来类比内存是最合适的。逻辑上的内存就好象是一栋无限大的大楼内存的单元格就好象大楼中的一个个小房间。每个内存单元格的地址就好象每个小房间的房间号。内存中存储的内容就好象住在房间中的人一样。 逻辑上来说内存可以有无限大因为数学上编号永远可以增加无尽头。但是现实中实际的内存大小是有限制的例如32位的系统32位系统指的是32位数据线但是一般地址线也是32位这个地址线32位决定了内存地址只能有32位二进制所以逻辑上的大小为2的32次方内存限制就为4G。实际上32位的系统中可用的内存是小于等于4G的例如我32位CPU装32位windows但实际电脑只有512M内存
2.3、位和字节 内存单元的大小单位有4个位1bit 字节8bit 半字一般是16bit 字一般是32bit 在所有的计算机、所有的机器中不管是32位系统还是16位系统还是以后的64位系统位永远都是1bit字节永远都是8bit。
2.4、字和半字 历史上曾经出现过16位系统、32位系统、64位系统三种而且操作系统还有windows、linux、iOS等很多所以很多的概念在历史上曾经被混乱的定义过。 建议大家对字、半字、双字这些概念不要详细区分只要知道这些单位具体有多少位是依赖于平台的。实际工作中在每种平台上先去搞清楚这个平台的定义字是多少位半字永远是字的一半双字永远是字的2倍大小。 编程时一般根本用不到字这个概念那我们区分这个概念主要是因为有些文档中会用到这些概念如果不加区别可能会造成你对程序的误解。 在linuxARM这个软硬件平台上我们嵌入式核心课的所有课程中字是32位的。
2.5、内存位宽硬件和逻辑两个角度 从硬件角度讲硬件内存的实现本身是有宽度的也就是说有些内存条就是8位的而有些就是16位的。那么需要强调的是内存芯片之间是可以并联的通过并联后即使8位的内存芯片也可以做出来16位或32位的硬件内存。 从逻辑角度讲内存位宽在逻辑上是任意的甚至逻辑上存在内存位宽是24位的内存但是实际上这种硬件是买不到的也没有实际意义。从逻辑角度来讲不管内存位宽是多少我就直接操作即可对我的操作不构成影响。但是因为你的操作不是纯逻辑而是需要硬件去执行的所以不能为所欲为所以我们实际的很多操作都是受限于硬件的特性的。例如24位的内存逻辑上和32位的内存没有任何区别但实际硬件都是32位的都要按照32位硬件的特性和限制来干活。
3、内存编址和寻址、内存对齐
3.1、内存编址方法 内存在逻辑上就是一个一个的格子这些格子可以用来装东西里面装的东西就是内存中存储的数每个格子有一个编号这个编号就是内存地址这个内存地址一个数字和这个格子的空间实质是一个空间是一一对应且永久绑定的。这就是内存的编址方法。 在程序运行时计算机中CPU实际只认识内存地址而不关心这个地址所代表的空间在哪里怎么分布这些实体问题。因为硬件设计保证了按照这个地址就一定能找到这个格子所以说内存单元的2个概念地址和空间是内存单元的两个方面。
3.2、关键内存编址是以字节为单位的 我随便给一个数字例如说7然后说这个数字是一个内存地址然后问你这个内存地址对应的空间多大这个大小是固定式就是一个字节8bit。 如果把内存比喻位一栋大楼那么这个楼里面的一个一个房间就是一个一个内存格子这个格子的大小是固定的8bit就好像这个大楼里面所有的房间户型是一样的。
3.3、内存和数据类型的关系 C语言中的基本数据类型有char short int long float double int 整形整数类型这个整就体现在它和CPU本身的数据位宽是一样的例如32位的CPU整形就是32位int就是32位。 数据类型和内存的关系就在于数据类型是用来定义变量的而这些变量需要存储、运算在内存中。所以数据类型必须和内存相匹配才能获得最好的性能否则可能不工作或者效率低下。 在32位系统中定义变量最好用int因为这样效率高。原因就在于32位的系统本身配合内存等也是32位这样的硬件配置天生适合定义32位的int类型变量效率最高。也能定义8位的char类型变量或者16位的short类型变量但是实际上访问效率不高。 在很多32位环境下我们实际定义bool类型变量实际只需要1个bit就够了都是用int来实现bool的。也就是说我们定义一个bool b1;时编译器实际帮我们分配了32位的内存来存储这个bool变量b1。编译器这么做实际上浪费了31位的内存但是好处是效率高。 问题实际编程时要以省内存为大还是要以运行效率为重答案是不定的看具体情况。很多年前内存很贵机器上内存都很少那时候写代码以省内存为主。现在随着半导体技术的发展内存变得很便宜了现在的机器都是高配不在乎省一点内存而效率和用户体验变成了关键。所以现在写程序大部分都是以效率为重。
3.4、内存对齐 我们在C中int a;定义一个int类型变量在内存中就必须分配4个字节来存储这个a。有这么2种不同内存分配思路和策略 第一种0 1 2 3 对齐访问 第二种1 2 3 4 或者 2 3 4 5 或者 3 4 5 6 非对齐访问 内存的对齐访问不是逻辑的问题是硬件的问题。从硬件角度来说32位的内存它 0 1 2 3四个单元本身逻辑上就有相关性这4个字节组合起来当作一个int硬件上就是合适的效率就高。 对齐访问很配合硬件所以效率很高非对齐访问因为和硬件本身不搭配所以效率不高。因为兼容性的问题一般硬件也都提供非对齐访问但是效率要低很多。
4、C语言如何操作内存
4.1、C语言对内存地址的封装用变量名来访问内存、数据类型的含义、函数名的含义 例如在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语言中数据类型的本质含义是表示一个内存格子的长度和解析方法。 数据类型决定长度的含义我们一个内存地址0x30000000本来这个地址只代表1个字节的长度但是实际上我们可以通过给他一个类型(int)让他有了长度(4)这样这个代表内存地址的数字(0x30000000)就能表示从这个数字(0x30000000)开头的连续的n(4)个字节的内存格子了(0x30000000 0x30000001 0x30000002 0x30000003)。 数据类型决定解析方法的含义例如我有一个内存地址0x30000000我们可以通过给这个内存地址不同的类型来指定这个内存单元格子中二进制数的解析方法。例如我 (int)0x30000000含义就是(0x30000000 0x30000001 0x30000002 0x30000003)这4个字节连起来共同存储的是一个int型数据那么我(float)0x30000000含义就是(0x30000000 0x30000001 0x30000002 0x30000003)这4个字节连起来共同存储的是一个float型数据 之前讲过一个很重要的概念内存单元格子的编址单位是字节。 C语言中函数就是一段代码的封装。函数名的实质就是这一段代码的首地址。所以说函数名的本质也是一个内存地址。
4.2、用指针来间接访问内存 关于类型不管是普通变量类型int float等还是指针类型int * float *等只要记住 类型只是对后面数字或者符号代表的是内存地址所表征的内存的一种长度规定和解析方法规定而已。 C语言中的指针全名叫指针变量指针变量其实和普通变量没有任何区别。例如int a和int *p其实没有任何区别a和p都代表一个内存地址譬如是0x20000000但是这个内存地址0x20000000的长度和解析方法不同。a是int型所以a的长度是4字节解析方法是按照int的规定来的p是int *类型所以长度是4字节解析方法是int *的规定来的0x20000000开头的连续4字节中存储了1个地址这个地址所代表的内存单元中存放的是一个int类型的数。
4.3、用数组来管理内存 数组管理内存和变量其实没有本质区别只是符号的解析方法不同。普通变量、数组、指针变量其实都没有本质差别都是对内存地址的解析只是解析方法不一样。 int a; // 编译器分配4字节长度给a并且把首地址和符号a绑定起来。 int b[10]; // 编译器分配40个字节长度给b并且把首元素首地址和符号b绑定起来。 数组中第一个元素a[0]就称为首元素每一个元素类型都是int所以长度都是4其中第一个字节的地址就称为首地址首元素a[0]的首地址就称为首元素首地址。
5、内存管理之结构体
5.1、数据结构这门学问的意义 数据结构就是研究数据如何组织在内存中排布如何加工的学问。
5.2、最简单的数据结构数组 为什么要有数组因为程序中有好多个类型相同、意义相关的变量需要管理这时候如果用单独的变量来做程序看起来比较乱用数组来管理会更好管理。 例如 int ages[20];
5.3、数组的优势和缺陷 优势数组比较简单访问用下标可以随机访问。 缺陷1 数组中所有元素类型必须相同2 数组大小必须定义时给出而且一旦确定不能再改。
5.4、结构体隆重登场 结构体发明出来就是为了解决数组的第一个缺陷数组中所有元素类型必须相同。 我们要管理3个学生的年龄int类型怎么办 第一种解法用数组 int ages[3]; 第二种解法用结构体
struct ages
{int age1;int age2;int age3;
};
struct ages age; 分析总结在这个示例中数组要比结构体好。但是不能得出结论说数组就比结构体好在包中元素类型不同时就只能用结构体而不能用数组了。
struct people
{int age; // 人的年龄char name[20]; // 人的姓名int height; // 人的身高
}; 因为people的各个元素类型不完全相同所以必须用结构体没法用数组。
5.5、结构体内嵌指针实现面向对象 面向过程与面向对象。 总的来说C语言是面向过程的但是C语言写出的linux系统是面向对象的。 非面向对象的语言不一定不能实现面向对象的代码。只是说用面向对象的语言来实现面向对象要更加简单一些、直观一些、无脑一些。 用C、Java等面向对象的语言来实现面向对象简单一些因为语言本身帮我们做了很多事情但是用C来实现面向对象很麻烦看起来也不容易理解这就是为什么大多数人学过C语言却看不懂linux内核代码的原因。
struct s
{int age; // 普通变量void (*pFunc)(void); // 函数指针指向 void func(void)这类的函数
}; 使用这样的结构体就可以实现面向对象。 这样包含了函数指针的结构体就类似于面向对象中的class结构体中的变量类似于class中的成员变量结构体中的函数指针类似于class中的成员方法。
6、内存管理之栈stack
6.1、什么是栈 栈是一种数据结构C语言中使用栈来保存局部变量。栈是被发明出来管理内存的。
6.2、栈管理内存的特点小内存、自动化 先进后出 FILO first in last out 栈 先进先出 FIFO first in first out 队列 栈的特点是入口即出口只有一个口另一个口是堵死的。所以先进去的必须后出来。 队列的特点是入口和出口都有必须从入口进去从出口出来所以先进去的必须先出来否则就堵住后面的。
6.3、栈的应用举例局部变量 C语言中的局部变量是用栈来实现的。 我们在C中定义一个局部变量时int a编译器会在栈中分配一段空间4字节给这个局部变量用分配时栈顶指针会移动给出空间给局部变量a用的意思就是将这4字节的栈内存的内存地址和我们定义的局部变量名a给关联起来对应栈的操作是入栈。 注意这里栈指针的移动和内存分配是自动的栈自己完成不用我们写代码去操作。 然后等我们函数退出的时候局部变量要灭亡。对应栈的操作是弹栈出栈。出栈时也是栈顶指针移动将栈空间中与a关联的那4个字节空间释放。这个动作也是自动的也不用人写代码干预。
6.4、栈的优点 栈管理内存好处是方便分配和最后回收都不用程序员操心C语言自动完成。 分析一个细节C语言中定义局部变量时如果未初始化则值是随机的为什么 定义局部变量其实就是在栈中通过移动栈指针来给程序提供一个内存空间和这个局部变量名绑定。因为这段内存空间在栈上而栈内存是反复使用的脏的上次用完没清零的所以说使用栈来实现的局部变量定义时如果不显式初始化值就是脏的。如果你显式初始化怎么样 C语言是通过一个小手段来实现局部变量的初始化的。
int a 15; // 局部变量定义时初始化
//C语言编译器会自动把这行转成
int a; // 局部变量定义
a 15; // 普通的赋值语句
6.5、栈的约束 预定栈大小不灵活怕溢出。 首先栈是有大小的。所以栈内存大小不好设置。如果太小怕溢出太大怕浪费内存。这个缺点有点像数组 其次栈的溢出危害很大一定要避免。所以我们在C语言中定义局部变量时不能定义太多或者太大例如不能定义局部变量时 int a[10000]; 使用递归来解决问题时一定要注意递归收敛
7、内存管理之堆heap
7.1、什么是堆 堆heap是一种内存管理方式。内存管理对操作系统来说是一件非常复杂的事情因为首先内存容量很大其次内存需求在时间和大小块上没有规律操作系统上运行着的几十、几百、几千个进程随时都会申请或者释放内存申请或者释放的内存块大小随意。 堆这种内存管理方式特点就是自由随时申请、释放大小块随意。堆内存是操作系统划归给堆管理器操作系统中的一段代码属于操作系统的内存管理单元来管理的然后向使用者用户进程提供APImalloc和free来使用堆内存。 我们什么时候使用堆内存需要内存容量比较大时需要反复使用及释放时很多数据结构譬如链表的实现都要使用堆内存。
7.2、堆管理内存的特点大块内存、手工分配使用释放 特点一容量不限常规使用的需求容量都能满足。 特点二申请及释放都需要手工进行手工进行的含义就是需要程序员写代码明确进行申请malloc及释放free。如果程序员申请内存并使用后未释放这段内存就丢失了在堆管理器的记录中这段内存仍然属于你这个进程但是进程自己又以为这段内存已经不用了再用的时候又会去申请新的内存块这就叫吃内存称为内存泄漏。在C/C语言中内存泄漏是最严重的程序bug这也是别人认为Java/C#等语言比C/C优秀的地方。
7.3、C语言操作堆内存的接口malloc free 堆内存释放时最简单直接调用free释放即可。 void free(void *ptr); 堆内存申请时有3个可选择的类似功能的函数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)); 数组定义时必须同时给出数组元素个数数组大小而且一旦定义再无法更改。在Java等高级语言中有一些语法技巧可以更改数组大小但其实这只是一种障眼法。它的工作原理是先重新创建一个新的数组大小为要更改后的数组然后将原数组的所有元素复制进新的数组然后释放掉原数组最后返回新的数组给用户 堆内存申请时必须给定大小然后一旦申请完成大小不变如果要变只能通过realloc接口。realloc的实现原理类似于上面说的Java中的可变大小的数组的方式。
7.4、堆的优势和劣势管理大块内存、灵活、容易内存泄漏 优势灵活 劣势需要程序员去处理各种细节所以容易出错严重依赖于程序员的水平。
8、复杂数据结构
8.1、链表、哈希表、二叉树、图等 链表是最重要的链表在linux内核中使用非常多驱动、应用编写很多时候都需要使用链表。所以对链表必须掌握掌握到会自己定义结构体来实现链表、会写链表的节点插入前插、后插、节点删除、节点查找、节点遍历等。至于像逆序这些很少用掌握了前面那几个这个也不难。 哈希表不是很常用一般不需要自己写实现而直接使用别人实现的哈希表比较多。对我们来说最重要的是要明白哈希表的原理、从而知道哈希表的特点从而知道什么时候该用哈希表当看到别人用了哈希表的时候要明白别人为什么要用哈希表、合适不合适有没有更好的选择 二叉树、图等。对于这些复杂数据结构不要太当回事。这些复杂数据结构用到的概率很小在嵌入式开发中其实这些数据结构被发明出来就是为了解决特定问题的你不处理特定问题根本用不到这些没必要去研究。
8.2、为什么需要更复杂的数据结构 因为现实中的实际问题是多种多样的问题的复杂度不同所以需要解决问题的算法和数据结构也不同。所以当你处理什么复杂度的问题就去研究针对性解决的数据结构和算法当你没有遇到此类问题或者你工作的领域根本跟这个就没关系时就不要去管了。
8.3、数据结构和算法的关系 数据结构的发明都是为了配合一定的算法算法是为了处理具体问题算法的实现依赖于相应的数据结构。 当前我们说的算法和纯数学是不同的算法是基于数学的大学计算机系研究生博士生很多本科都是数学相关专业的因为计算机算法要求以数学算法为指导并且结合计算机本身的特点来改进最终实现一个在计算机上可以运行的算法意思就是用代码可以表示的算法。
8.4、应该怎样学习这部分 从上面表述大家应该明白以下事实 1. 数据结构和算法是相辅相成的要一起研究。 2. 数据结构和算法对嵌入式来说不全是重点不要盲目的跑去研究这个。 3. 一般在实际应用中实现数据结构和算法的人和使用数据结构和算法的人是分开的。实际中有一部分人的工作就是研究数据结构和算法并且试图用代码来实现这些算法表现为库其他做真正工作的人要做的就是理解、明白这些算法和数据结构的意义、优劣、特征然后在合适的时候选择合适的数据结构和算法来解决自己碰到的实际问题。 举个例子linux内核在字符设备驱动管理时使用了哈希表hash table散列表。所以字符设备驱动的很多特点都和哈希表的特点有关。