导图社区 程序是怎么跑起来的
程序是怎么跑起来的? 一起来看看CPU、数据是二进制表示的、计算机计算小数会出错的原因、内存、内存和磁盘的关系、压缩数据、程序运行的环境、从源文件到可执行文件、操作系统和应用的关系、通过汇编了解程序实际构造... ...
编辑于2023-04-04 11:29:46 辽宁程序是怎么跑起来的
CPU
CPU的内部构造
控制器
把内存上的指令,数据读入寄存器,根据指令执行结果控制计算机
内存,通过控制芯片与CPU相连,主要负责储存数据和指令,断电数据自动清理。像一些鼠标,键盘,磁盘等都是指令和内容(就是我们平常所说的运行内存,内存条)
运算器
运算从内存读取入寄存器的数据
时钟
CPU开始计时的时钟信号
寄存器
暂存指令,数据等处理对象, 内存的一种,一个CPU有20~100
CPU是寄存器的集合体
汇编语言采用助记符编写程序,汇编程序转机器语言称汇编,反之为反汇编
机械语言程序是通过寄存器来处理的,所以对程序员来说CPU是寄存器的集合。
高级语言会在编译后转化为机械语言,最后通过CPU处理
寄存器内容有指令和数据,数据有用于计算的数据,表示地址的等,指令的有指令寄存器,种类不同所在的寄存器就不同
决定程序的程序计数器(CPU寄存器的一种)
CPU的控制器参照程序计数器的数值,从内部读取命令并执行,也就是说计数器决定程序流程
条件分支和循环机制
顺序执行
按照地址内容顺序执行指令
条件分支
根据条件执行任意地址的指令
循环
重复执行同一指令
跳转指令
会根据判断条件运算结果来判断是否跳转,判断的结果保存在标志寄存器,CPU在参考标志寄存器之后决定是否跳转
函数的调用机制
原因
函数的调用需要完成函数内部处理之后,返回函数调用点的下一个地址,因此通过跳转到函数入口地址,处理流程就不知道返回至哪里了。
call指令return命令
call指令会把调用函数后要执行的指令地址存储在栈的主存内。程序结束后通过函数的出口执行return命令,把保存在栈中的地址设定到程序计数器。
简单来说就是用call指令把函数下一个地址存入栈,然后等函数运行完成之后,在通过return返回到栈中的地址
通过地址和索引实现数组
就相当于基址寄存器,为数组第一个固定地址,变址寄存器变化的范围为数组的范围地址。通过索引地址,查看数据
基址寄存器
存储数据内存的起始位置
变址寄存器
存储基址寄存器的相对位置
CPU的处理
CPU执行的主要指令类型
数据转送指令
就是各种内存,寄存器,外围设备(鼠标键盘硬盘等)之间的数据读写操作
运算指令
用累加寄存器执行算术运算,逻辑运算,比较运算和位移运算等。
跳转指令
实现条件分支,循环,强制跳转等。
call/return指令
函数的调用/返回调用前的地址
数据是二进制表示的
用二进制的原因
IC集成电路
IC有不同的形状,有数个至几百个引脚,只有5v和0v两状态,两个状态决定了信息数据只能用二进制处理
CPU内存都是IC的一种
二进制
二进制的的位数一般是8的倍数,8bit/位为一个字节(bite),字节是最基础的信息计量单位,位是最小的。
内存磁盘都用字节来储存信息。用位则无法读写。
多少位的处理器表示一次可以处理多少位的二进制信息,没用完的位用0填补
不管什么信息,都会编译成二进制。计算机不会区分他是什么信息,只根据程序指令进行处理
二进制转十进制
对应权位乘以对应的位数的值可以算对应位十进制的值
二进制的基数是二,十进制的基数是十。10n次幂,这个就是位权
权数的幂,第一位从0次幂开始,以此类推,0次幂为一
位移运算与乘除运算的关系
计算机中可以让二进制左右移代替乘除运算
移位操作后最高位或最低位直接丢弃
左移会扩大2,4,6, 右移则缩小2,4,6
和十进制左右移缩小扩大10倍是一样的,所以用这种方法代替乘除运算
补数
总而言之补数就是用正数来表示负数
正负
一般用最高位表示符号位,0为正,1为负
补数就是用正数来表示负数,补数求解变换方法为,取反➕1
取反加一和原来的值相加为0
short将最高位用来判断正负,而unsigned将最高位看做更大的数
逻辑左移与算术右移
二进制的值表示图形而非数值,移位要在最高位上补0。这种就称逻辑逻辑右移。
将二进制作为带符号的数值时,移位后要在最高位补符号位的值0或1。这个就算术右移
符号扩充,如果想表示11111111这样的正数,就将其转换成16位的二进制就行了前面加8个0,如果要表示负数则所有的高位只需要用1填充高位就行了。如16个1。
逻辑运算的窍门,(1为true0为false)
把1和0当做开关ON,OFF。
not,非
就是not1为0,not0为1
and,与
1and0为0
or,或
1or0为1
xor,异或
不同为1,相同为0
计算机计算小数会出错的原因
0.1➕100次得不到10
用二进制表示小数
小数部分和正数部分基本一样,只不过小数后面的位的幂是负幂,将所有位值加起来就得到十进制小数部分了。
运算出错的原因
因为二进制的小数对应十进制数有些是无法表示的,如果增加小数位相应的十进制个数也会增加,所以只有无限接近。就比如无法用十进制表示1/3是一样的道理。计算机就会从中折断四舍五入。
什么是浮点数
浮点数
浮点数是指用符号,尾数,基数和指数四部分表示的小数±m*n次幂
因为计算机是二进制所以基数为二,关于符号位的解释前面有。
双精度浮点数
一位表示符号部分,11位表示指数,52位表示尾数
单精度浮点数
一位表示符号,8位表示指数,23位表示尾部
尾数部分
用的是将小数点前面的值固定为1的正则表达式。(具体说明在下一节)
指示部分
则用的是excess系统表现,(具体说明在下一节)
正则表达式和excess系统
0(符号位0为正)-01111110(指数,8位,根据excess表示为负一次幂)-1000000000000000000000000(尾数,23位,根据正则表达式实际是1.1,小数点前面的1被省略了) 二进制是:1.1x2的负一次幂 十进制是:1.5x2的负一次幂为0.75
正则表达式
将二进制表示的小数左移或右移使小数点前面为1,用2的指数次幂乘以尾数来使数值不变,因为一直是1,所以会被省略,以此多一位表示尾数。
例如 1011.0011 1.0110011:移动为小数点前面为1 10110011:把固定1省略可以让小数点后长度增加一位 这是尾数的表示方式 随着尾数的移动指数也会发生变化使数值不变 符号位是独立的,不在尾数里面
excess系统
取中间的值做为0次幂,以此区分正负次幂。
假如是32位处理器,指数部分则是8位,8位可以表示的值为255,从中间127截断为0次幂,126为负1次幂,128为正1次幂,以此类推,64位处理器同理。
怎么避免计算错误
回避策略
根据程序的目的不同微小的误差并不会造成什么问题
把小数转化成整数计算
例如0.1➕100次是不会得到整数10的(计算机二进制不能表示0.1只能无限接近),而将他扩大十倍后,在加一百次,最后除以10就可以得到整数10了
BCD
就是用二进制表示十进制的方法,用4位二进制来表示0~9的一位数字,在财务计算不能出现误差的一定要BCD,或者小数转整数的方法
二进制和十六进制
用十六进制表示二进制,位数缩小四倍,位数变少,看起来跟清晰
表示小数也是4位变一位,位数不够用0补。
对应权位乘以对应的位数的值可以算相对位十进制的值
权数的幂,负一位从负1次幂开始,以此类推。
内存
内存的物理机制
内存IC内部有大量可以储存8进制的地方,通过地址指定这些场所,之后就能今进行数据的读写
内存IC
内存IC中有电源,地址信号,数据信号,控制信号用于输入输出的大量引脚。
内存IC(RAM)的引脚
(RAM)内存IC的引脚VCC和GND是电源(给vcc接入5V,gnd接入0V),A0~A9是地址信号的引脚,D0~D7是数据信号的引脚,RD和WR是控制信号的引脚,引脚电压5V为1,0V为0。
内存IC能存多少数据?
地址引脚有十个可以指定1024个地址,地址引脚是8位1字节,所以1024个字节为1KB所以这个内存IC能存1KB的数据(通常情况下IC会有更多的地址引脚,该内存IC只是方便解释。)
RD和WR
WR为1是写入,RD为1是读出,同时为0就不能进行写入和读出的操作
内存的逻辑模型是楼房
内存模型1024层楼,每层有一字节的数据
数据类型
不同的数据类型站不同层数的楼层,C语言中,char表示一字节的长度,short二字节长度,long四字节长度,分别占的是1层楼,2层楼,4层楼,在C语言中double类型64位8字节是最大的楼层。
中低字节序,根据数据大小从小到大指定低位地址到高位地址。
简单的指针
指针是一种变量,不是数据的值,而是存储着数据的内存的地址。Windows上程序是32位4字节的内存地址,这种情况下指针长度也是32位
浮动主题
例如long类型F指针指向100,使用F时就能读写100~103的地址。指针的数据类型表示一次可以读取的长度
数组是高效使用内存的基础
数组是指多个同样数据类型的数据在内存中连续排列的方式,可以用索引进行读写。
不同类型的数组如short,long会以2个字节,4个字节对内存进群行读写
不同类型数组对应的物理内存不一样,char1字节和楼层完全相等的,short2字节相当于用了两层楼做数组,long4字节相当于用了4层楼做数组。这样就不用逐字节的读写了。能提高效率
通过索引使用数组和内存的物理读写没有什么太大区别
栈,队列以及环形缓冲区
栈,队列
栈和队列都可以不通过指定地址和索引对数组的元素进行读写,需要临时保存计算过程的数据,连接在计算机上的设备或者输入输出的数据时,可以通过这些方法来使用内存。
这里的栈和队列的实体是数组 栈和队列的区别就是一个是后入先出,一个先入先出。 在内存中预留好栈和队列的空间,确立好顺序,就可以不用指定地址和索引了。
用函数对数组读写可能会涉及到索引的管理,单从使用函数的角度上就不用考虑了。
队列和栈的函数基本方法
栈
写入数据为Push(),读出为Pop()
队列
写入为EnQueue(),读出为DeQueue()
栈的LIFO方式
顾名思义,最后的数据Last In会最先出来,最前的数据First Out最后出来,先入后出
为了保护数据的目的,这种内存机制就是栈,有时当我们需要暂时舍弃数据,随后在还原时,会使用栈。
队列的FIFO方式
同上,先入先出
这种方式也称排队,类似于现实生活中的排队当人数过多时排队能起到很好的缓冲作用,排队能起到很好的缓冲机制。程序也是一样的,处理多个程序的数据时,就会用到队列这种处理数据的方法
队列是以环形缓冲区来实现的,一个环形的数组,读取一个之后尾部就空了一个位置,然后头部又写一个位数据,这样头部就一直增加,尾部一直减少,头和尾就连起来了。
链表使元素的追加和删除更容易
在元素的本身附带上下一个元素的索引,就可以实现链表,尾部没有数据用-1来填充
链表删除,增加元素,是通过改变该元素的索引,可以想象成很多很多箭头,当你要删掉一个元素时,你只需要将指向该元素的上一个元素的索引改成该元素的索引,替它指向下一个(这里的索引是指向下一个元素的)。此时该元素已经没有元素指向它了,它就永远指向不了下一个元素了,它从逻辑上来看已经被删除了。 假如你要增加一个元素,你先要确定追加在谁后面,然后改变它的索引指向该元素,该元素的索引要变成它的索引替它指向它原本指向的元素,这样你就在它的后面增加了一个元素。(这里的元素的索引都是指向下一个元素位置)实在不能理解看书
链表的好处,就是不用大规模的移动元素,如果每次增加或删除都要移动成千上万的元素,就是是计算机也吃不消的,而通过链表的指向来追加或删除数据就显得毫不费事了。
二叉查找树
二叉树就是在链表的基础上追加一个分成左右两个方向表现形式,例如大的指右边,小的指左边,就是将一条长长的链表变成一棵二叉树,如果要查找的数据比当前数据大就让它指向右边,小则指左边。两个索引根据逻辑判断去指向下一个元素。二叉树是由链表构造发展出来的表现形式,在追加,删除元素也同样有效。
二叉树能加快找到目标数据的速度
本章节作者寄语
大家要理解为什么要进行这样的处理,另外记住数值才是进行这些处理的关键
内存和磁盘的关系
读入内存才能运行
储存程序方式
程序保存在储存设备中,有序的被读出来。
磁盘的程序无法直接运行,这是因为负责解析和运行程序的CPU,要通过程序计数器指定地址才能读出程序即使CPU可以直接读出磁盘程序,也会因为磁盘读取速度慢而降低运行速度,总之磁盘中的程序读入到内存才能运行
磁盘缓存
磁盘缓存指把从磁盘中读出的数据存储到内存空间中的方式,可以改善磁盘数据访问速度。
这种缓存把低速数据设备保存到高速设备,需要时就可以直接读出了。这种的实例就是web浏览器,把低速的网络数据保存到相对高速的磁盘
虚拟内存把磁盘当内存用
虚拟内存
虚拟内存是指把磁盘的一部分作为假象内存使用。与磁盘缓存相对,缓存实际是内存,虚拟内存实际是磁盘
方法
实现虚拟内存就必须把实际内存的内容,和磁盘的虚拟内存的内容部分进行部分置换,同时运行程序
简单来说,内存把暂时不要运行的部分,放到虚拟内存中去,再从虚拟内存中取出现在要运行的部分,以页或段为单位。
分页式和分段式
Windows采用分页是就是把程序按照一定大小页进行分割,以页为单位在内存和磁盘之间进行置换
Windows在磁盘上提供了虚拟内存的页文件,文件的大小就是虚拟内存的大小,一般是内存的一到两倍。
节约内存的方法
GUI
以图形用户界面为基础的Windows,像Windows这样可以进行可视化操作的方式称为图形用户界面
由于GUI过大,导致内存不够用,虚拟内存又伴随着低速磁盘访问,导致应用运行会变的卡顿。
解决方法
通过DLL文件实现函数共有
DLL就是在程序运行时可以动态加载Library(函数和数据的集合)文件
多个应用可以共用同一个DLL可以达到节约内存的效果。
就比如两个程序有同一个函数,那这个函数就占用了两份内存空间。可以把这个函数设置成DLL就可以实现共享,节省了一份空间。
Windows本身就是多个DLL文件的集合体,在安装应用时,DLL文件也会被追加,应用则会通过DLL文件运行。还有一个好处就是不变更exe文件的情况下,通过升级DLL文件就能更新
调用_stdcall
Windows的DLL都是_stdcall调用的,C里面用的是C特有的调用方式。
C中,栈清理处理是把不需要的数据接收和传递函数参数时使用的内存上的栈区域清理出去。该命令是编译器附加在函数调用方。
例如main()中调用了MyFunc(),按照默认设置栈清理在main()上,同一个程序中可能会多次调用MyFunc(),导致同一处理反复进行,这就造成了内存浪费。
使用_stdcall把栈清理变为被动调用的函数一方进行,只需要进行一次清理。
磁盘的物理结构
磁盘通过把其物理表面划分多个空间来使用的,有扇区方式和可变长方式两种
一种是固定长度空间,另一种是长度可变的空间,Windows的硬盘和软盘都是扇区方式
一个扇区是512字节,软件方面对硬盘进行读写的是扇区的正数倍的簇,磁盘空间越大簇的容量越大。
1簇可以等于512字节,1KB,2KB等等
不管是硬盘软盘,不同的文件不能放在一簇中,无论多小的文件都会占用一簇的空间
虽然有点浪费,但规定如此,如果簇的容量减少,那么硬盘访问的次数就会变多。 硬盘表示扇区还是很有必要的,如果簇的容量过小硬盘的整体容量也会变小,扇区和簇的大小是由处理速度和储存容量觉得的。
压缩数据
文字以字节为单位保存
文件就是字节的集合,在文件中的字节数据都是连续储存的。包括文字,和图片都是字节数据是集合
RLE算法机制
把文件内容用“数据x出现的次数”的形式来表示压缩方法,例如有10个A普通的文件是A有10个重复的,而RlE就把他用Ax10来表示十个A。图形也适用。
缺点
这种比较适合数据相同的图片文件等,不太适合文本压缩,因为如果相同字符连续出现的次数不多时,还会导致文件变大
通过莫斯科编码来看哈夫曼算法
哈夫曼的关键
多次出现的数据用小于8位的字节数来表示,不常用数据用超过8位的字节来表示,实现这一处理压缩内容会很复杂,但压缩率也是很高的。
相当于把一个字节拆开了,来分配编码。
莫斯科编码通过长点,短点和停来表示字符,停表示字符空格,长点短点对应的是1和0,通过对位的数据来查看字符。例如1011表示A
0,1,和停都算做一位
用二叉树实习哈夫曼编码
哈夫曼编码是基于哈夫曼树的,哈夫曼树就是 “实现带权路径长度达到最小”(实现位数最小化) 权相当于出现字符的次数 路径相当于1或0的枝干 总枝干相当于字符编码 枝干数越小字符编码越小所占的位越小 出现次数乘以枝干树等于位数。
哈夫曼算法
为各压缩对象分别构造最佳的编码体系
例如A字母最多,就跟给他分配长度最短的编码。以此类推 用这个方法压缩过的文件中,储存着哈夫曼编码信息
但是这种压缩有着一个问题,那就是字符的之间没有分隔符,1001可能还表示着100和1,用二叉树实习哈夫曼就会有这样的问题了。
二叉树
简单来说就是出现次数越多的数据分支越小,他的位数也就越小。 如果数据种类过多分支也会变多,位数也会变大。由此来实现哈夫曼算法。
哈夫曼算法能大幅提升压缩比例
用枝条连接数据时,从出现频率低的开始,这意味着频率越低,枝条数越多
反正就是你出现频率越大,你的枝条越小。分之,由此得最优的编码号。
读取数据后就会以位为单位进行排查,与哈夫曼树就行比较,然后逐一向下比较,最后到叶。所有能将数据进行区分的原因。
可逆压缩和非可逆
顾名思义,压缩后可逆就是可还原,非可逆就是不能还原,会丢失数据
程序必须是可逆压缩,不然会导致程序无法运行
图像文件
BMP
完全未压缩,显示器,打印机输出的点都是可以直接映射的
JPEG
非可逆压缩,因此还原有一部分是迷糊的
TIFF
可逆压缩,他的文件里包涵很多标签信息,可以选择压缩格式,所有他所占的内存比BMP格式还大
GIF
可逆压缩,由于他的颜色不能超过256色的限制,所以还原会有一点缺失
程序运行的环境
运行环境等于操作系统加硬件
很多软件都要指定的运行环境才运行
同一类型的硬件可以选择安装多种操作系统
例如AT兼容机,可以安装Windows也可以安装Linux。
CPU是运行环境重要的参数,它只能解释自身固有的机器语言
CPU有x86,MIPS,SPARC等他们各自的机器语言不同。 一些软件为了保证正常运行,对CPU也有要求。
机器语言又叫本地代码。C语言等编写的程序,仅仅只是文本文件,我们称之为源代码。只有通过对源代码进行编译才能得到本地代码
Windows应用程序的本地代码,通常是exe和dll文件
Windows克服CPU以为的硬件差异的历史
主要就是Windows解决了软件运行的环境,使程序员不用再注意内存和I/O地址的不同构成了。 只需要对系统就行开发了。
之前计算机机型各不相同(只有CPU相同),但是内存和I/O地址构成都是不同的,因此搭载系统都是不同机型的MS-DOS,x86有同外来设备进行输入输出的I/O地址(I/O地址分配),至于外围设备分配到什么样的地址,则有机型决定
当时购买软件就必须是各机型专用的软件,因为那时的应用软件中存在直接操作硬件的部分,原因是因为MS-DOS功能不完善,提供程序运行速度
随着Windows的广泛使用,这种局面得到改善,只要Windows能运行,用于Windows的软件能再各个机型上运行,在Windows中软件是对Windows发送指令运行的,不过Windows需要为不同机型提供不同的Windows系统
不同系统API不同
应用程序对操作系统传递指令的途径称为API
API通过了任何程序都可以使用的函数组合,因为不同系统的api有差异,因此在移植程序时,要重写用到api部分,像键盘,鼠标,显示器等输入输出功能都是通过api进行的。
CPU种类不同,机器代码就不同,所有需要利用能生成CPU专用本地代码编译器。
程序的本地代码由系统和环境决定
FreeBSD Port
Unix系列操作系统FreeBSSD中有Ports机制能结合当前的硬件环境编译应用的源代码,进而得到可运行的本地代码,应用如果没有源代码就会自动使用FTP连接站点下载代码,FreeBSD上的应用源代码,大部分都是C语言写的,而Unix系统中,都带有C编译器。
简单来说Ports能够克服包涵CPU在内的所有硬件系统差异
利用虚拟机获得其他的操作系统环境
就是在其他硬件机型中安装了其他的操作系统,例如苹果电脑装了Windows系统
提供相同环境的Java虚拟机
Java通过将Java源代码编译后得到字节代码,通过Java虚拟机一边转化为本地代码一边运行
这样实现了同样的字节代码在不同的环境运行,结合操作系统和硬件做成的Java虚拟机可以使同样的字节代码的应用在任何环境下运行了
缺点就是部分字节代码应用,还是不能在Java虚拟机上运行,Java虚拟机多了一到流程运行速度变慢
BIOS和引导
BIOS系统有键盘,磁盘,显卡等基本控制程序外,还有引导程序。
开机后BIOS检测硬件是否正常,没问题后启动引导程序,将硬盘的OS加载到内存中运行,OS再启动软件
从源文件到可执行文件
计算机只能运行本地代码
计算机不能运行源代码,只能运行本地代码,所以源代码都要转化为机器语言
本地代码的内容
Windows的exe使用就是本地代码,把exe内容Dump一下转化为二位16进制。本地代码就是各种数值的罗列,计算机指令也是数值的罗列,这就是本地代码。
编译器负责转化源代码
不同语言的源代码都有自己专属编译器,CPU型号不同能处理的本地代码不同,所以编译器和CPU类型的种类也有关。但是一个好处就是同样的源代码,可以翻译成不同CPU处理的本地代码。
因为编译器也是程序,也要运行环境,例如Windows的C语言编译器,和Linux的C语言编译器。有一种交叉编译器,处理不同CPU所使用的本地代码。
CPU,语言,操作系统
仅靠编译无法得到可执行文件
在源代码中使用函数,源代码都没有这些函数的处理内容。因此在编译过程成exe文件时,这时要进行把函数的处理内容的目标文件和半成品的文件结合,否则就不完整,exe文件无法运行。把多个目标文件结合生成一个exe文件的处理就是链接。在经过链接目标文件的链接器处理后,才会生成exe
启动及库文件
Sample.obj是编译后的目标文件。c0w32.obj文件记述的是所有程序起始位置相结合的处理内容,也叫程序的启动,由BorlandC++提供。
即使程序没有调用函数,也要进行链接,并和启动结合起来。
函数的处理内容的目标文件都在库文件里面,库文件指多个目标文件集成保存在一个文件里,连接器指定库文件后,会抽取目标文件,同其他目标文件结合生成exe文件
标准函数是通过库文件和编译器一起通过的,库文件里面很多标准函数,只需要指定那么几个库文件就可以了。
DLL文件及导入库
Windows以函数的形式为应用通过了很多功能,这些函数就是API。像一些函数的目标文件文件库,本身没有实体,它的实体在Windows的dll文件中,执行时从dll文件调出的函数。这样的库文件叫导入库。
那些有实体功能与exe文件结合的的库文件叫静态链接库。
可执行文件运行时的必要条件
exe文件没有指定函数和和变量的实际内存地址,exe文件给变量和函数的是虚拟内存地址,在程序运行时,虚拟地址会转化为实际地址。链接器会在exe文件开头增加,转化内存地址的必要信息,称为再配置信息。
子主题
程序加载时会生成栈和堆
栈是又来储存函数内部临时使用的变量(局部变量),以及函数调用时所使用的内存区域。
堆是用来存储程序运行时的任意数据及对象的内存领域
加载到内存的程序由用于变量的空间,用于函数的空间,用于栈的空间,用于堆的空间组成。
栈和堆的相似都是在运行内存时申请分配的,不同的地方在于栈会自动释放内存,而堆不会,需要提供函数进行释放,如果不释放,它会一直残留。这种现象称之为内存泄露。可能会使电脑内存不足导致宕机。
Q&A
编译器在运行前对所有程序进行解释,而解释器一边运行一边解释
分割编译指将程序分成多个源代码,通过链接成一个exe文件,便于管理
有的编译器可以提供Build生成exe文件,Build指的是连续执行编译和链接
DDL好处多个程序公用,节省内存,修改函数时不用重新链接(静态链接)使用这个函数的程序了。
不链接导入库,可以使用API调用dll文件函数。
叠加链接指将不会同时执行的函数,加载到同一个地址,通过叠加链接器可以实现,能节省内存
垃圾回收指把不需要的堆内存的数据进行清理,C语言用的是free(),以C语言开发出来的其他语言中,程序运行会自动进行垃圾回收,避免忘记释放内存
操作系统和应用的关系
操作系统历史
早期没有操作系统,人们开发了有加载和运行功能的监控程序,慢慢的人们将一系列的输出输入程序追加到监控程序中,早期的操作系统也就完成啦。因此操作系统是多个程序的集合
要意识到操作系统的存在
有了操作系统后,程序员和硬件就被系统隔开了。不过应该要掌握基本的硬件知识,并借助操作系统进行抽象化,可以提高效率。
应用可执行文件,计算机的CPU可以直接解释并运行的本地代码,不过这些代码无法直接控制计算机的时钟IC及显示器的I/O等硬件的。在操作系统的运行环境下,通过操作系统间接控制硬件,基本上程序都是面相系统的而不是面相硬件,系统收到指令先解释,然后在对硬件进行控制
系统调用和高级编程语言的移植形
操作系统硬件功能由各种小函数提供,这些调用这些函数的行为叫系统调用。而一般语言函数的内部都有系统调用,系统调用在函数的内部执行。在不同操作系统下能使用相同的代码,只需要修改系统调用就行了(移植性)。
操作系统和高级编程语言使硬件抽象化
文件是操作系统对磁盘媒介空间的抽象化,相当于扇区抽象成了文件。
fopen()函数的返回值,该值称为文件指针。打开文件后系统会自动分配管理该文件读写的内存空间,通过指定文件指针对文件进行操作。
简单来说就是文件指针,指向文件,通过指定文件指针对文件进行编辑。
Windows系统特点
对程序员有意义
32位系统
处理不同位数的数据类型时,高位点数据使内存和磁盘的开销较大,低位反之
API函数集提供系统调用
API是联系应用程序和操作系统之间的接口,32位的API中,各参数及返回值都是32位。API进行系统调用,API通过多个DLL文件提供,DLL通过使用API调用或其动态调用请求某些服务,API实体都是C编写的。
GUI用户界面
用的时候是天堂,做的时候是地狱,之前的ms-dos应用处理流程,是程序员决定,而GUI决定流程,因此必须要写出任何顺序都能运行的应用,这才是GUI的难点
WYSIWYG实习打印输出
WYSIWYG指通过显示器显示输出和打印机打印输出,通Windows的WYSIWYG功能,实习同一个程序实习打印和显示两操作。
多任务功能
通过时钟分割技术实现多任务,一个任务执行另一个待机,相互转换,转换时间有时钟分割确定
通过网络功能及数据库功能
网络功能由标准功能通过,数据库也可能是后面追加的,被称为中间件,不是软件,和系统相近被称为系统软件。
即插即用
外部设备连接,系统会自动安装和设定用来控制该设备的设备驱动,往往在购买时里面通常收录着设备驱动程序(DLL与API)
通过汇编了解程序实际构造
汇编与本地代码
汇编与本地代码对应,汇编转本地通过汇编器,本地转汇编为反汇编,C语言不能反汇编因为它与本地代码不是一一对应的。
通过编译器输出的汇编语言的源代码
本地代码的伪指令
汇编的源代码是转换成本地代码的指令和针对汇编器的伪指令构成。
汇编语言语法是“操作码”加“操作数”
相当于1+1,1为操作数,+为操作码。
运行流程是,本地加载到内存,内存有本地代码的指令和数据后,传给CPU,然后我就CPU的寄存器进行处理
mov指令
两个操作数“储存地”和“读出源”
对栈进行push和pop
push是入栈pop是出栈
函数调用机制
详细看书
确保全局变量的内存空间
子主题
程序是怎么跑起来的
CPU
CPU的内部构造
控制器
把内存上的指令,数据读入寄存器,根据指令执行结果控制计算机
内存,通过控制芯片与CPU相连,主要负责储存数据和指令,断电数据自动清理。像一些鼠标,键盘,磁盘等都是指令和内容(就是我们平常所说的运行内存,内存条)
运算器
运算从内存读取入寄存器的数据
时钟
CPU开始计时的时钟信号
寄存器
暂存指令,数据等处理对象, 内存的一种,一个CPU有20~100
CPU是寄存器的集合体
汇编语言采用助记符编写程序,汇编程序转机器语言称汇编,反之为反汇编
机械语言程序是通过寄存器来处理的,所以对程序员来说CPU是寄存器的集合。
高级语言会在编译后转化为机械语言,最后通过CPU处理
寄存器内容有指令和数据,数据有用于计算的数据,表示地址的等,指令的有指令寄存器,种类不同所在的寄存器就不同
决定程序的程序计数器(CPU寄存器的一种)
CPU的控制器参照程序计数器的数值,从内部读取命令并执行,也就是说计数器决定程序流程
条件分支和循环机制
顺序执行
按照地址内容顺序执行指令
条件分支
根据条件执行任意地址的指令
循环
重复执行同一指令
跳转指令
会根据判断条件运算结果来判断是否跳转,判断的结果保存在标志寄存器,CPU在参考标志寄存器之后决定是否跳转
函数的调用机制
原因
函数的调用需要完成函数内部处理之后,返回函数调用点的下一个地址,因此通过跳转到函数入口地址,处理流程就不知道返回至哪里了。
call指令return命令
call指令会把调用函数后要执行的指令地址存储在栈的主存内。程序结束后通过函数的出口执行return命令,把保存在栈中的地址设定到程序计数器。
简单来说就是用call指令把函数下一个地址存入栈,然后等函数运行完成之后,在通过return返回到栈中的地址
通过地址和索引实现数组
就相当于基址寄存器,为数组第一个固定地址,变址寄存器变化的范围为数组的范围地址。通过索引地址,查看数据
基址寄存器
存储数据内存的起始位置
变址寄存器
存储基址寄存器的相对位置
CPU的处理
CPU执行的主要指令类型
数据转送指令
就是各种内存,寄存器,外围设备(鼠标键盘硬盘等)之间的数据读写操作
运算指令
用累加寄存器执行算术运算,逻辑运算,比较运算和位移运算等。
跳转指令
实现条件分支,循环,强制跳转等。
call/return指令
函数的调用/返回调用前的地址
数据是二进制表示的
用二进制的原因
IC集成电路
IC有不同的形状,有数个至几百个引脚,只有5v和0v两状态,两个状态决定了信息数据只能用二进制处理
CPU内存都是IC的一种
二进制
二进制的的位数一般是8的倍数,8bit/位为一个字节(bite),字节是最基础的信息计量单位,位是最小的。
内存磁盘都用字节来储存信息。用位则无法读写。
多少位的处理器表示一次可以处理多少位的二进制信息,没用完的位用0填补
不管什么信息,都会编译成二进制。计算机不会区分他是什么信息,只根据程序指令进行处理
二进制转十进制
对应权位乘以对应的位数的值可以算对应位十进制的值
二进制的基数是二,十进制的基数是十。10n次幂,这个就是位权
权数的幂,第一位从0次幂开始,以此类推,0次幂为一
位移运算与乘除运算的关系
计算机中可以让二进制左右移代替乘除运算
移位操作后最高位或最低位直接丢弃
左移会扩大2,4,6, 右移则缩小2,4,6
和十进制左右移缩小扩大10倍是一样的,所以用这种方法代替乘除运算
补数
总而言之补数就是用正数来表示负数
正负
一般用最高位表示符号位,0为正,1为负
补数就是用正数来表示负数,补数求解变换方法为,取反➕1
取反加一和原来的值相加为0
short将最高位用来判断正负,而unsigned将最高位看做更大的数
逻辑左移与算术右移
二进制的值表示图形而非数值,移位要在最高位上补0。这种就称逻辑逻辑右移。
将二进制作为带符号的数值时,移位后要在最高位补符号位的值0或1。这个就算术右移
符号扩充,如果想表示11111111这样的正数,就将其转换成16位的二进制就行了前面加8个0,如果要表示负数则所有的高位只需要用1填充高位就行了。如16个1。
逻辑运算的窍门,(1为true0为false)
把1和0当做开关ON,OFF。
not,非
就是not1为0,not0为1
and,与
1and0为0
or,或
1or0为1
xor,异或
不同为1,相同为0
计算机计算小数会出错的原因
0.1➕100次得不到10
用二进制表示小数
小数部分和正数部分基本一样,只不过小数后面的位的幂是负幂,将所有位值加起来就得到十进制小数部分了。
运算出错的原因
因为二进制的小数对应十进制数有些是无法表示的,如果增加小数位相应的十进制个数也会增加,所以只有无限接近。就比如无法用十进制表示1/3是一样的道理。计算机就会从中折断四舍五入。
什么是浮点数
浮点数
浮点数是指用符号,尾数,基数和指数四部分表示的小数±m*n次幂
因为计算机是二进制所以基数为二,关于符号位的解释前面有。
双精度浮点数
一位表示符号部分,11位表示指数,52位表示尾数
单精度浮点数
一位表示符号,8位表示指数,23位表示尾部
尾数部分
用的是将小数点前面的值固定为1的正则表达式。(具体说明在下一节)
指示部分
则用的是excess系统表现,(具体说明在下一节)
正则表达式和excess系统
0(符号位0为正)-01111110(指数,8位,根据excess表示为负一次幂)-1000000000000000000000000(尾数,23位,根据正则表达式实际是1.1,小数点前面的1被省略了) 二进制是:1.1x2的负一次幂 十进制是:1.5x2的负一次幂为0.75
正则表达式
将二进制表示的小数左移或右移使小数点前面为1,用2的指数次幂乘以尾数来使数值不变,因为一直是1,所以会被省略,以此多一位表示尾数。
例如 1011.0011 1.0110011:移动为小数点前面为1 10110011:把固定1省略可以让小数点后长度增加一位 这是尾数的表示方式 随着尾数的移动指数也会发生变化使数值不变 符号位是独立的,不在尾数里面
excess系统
取中间的值做为0次幂,以此区分正负次幂。
假如是32位处理器,指数部分则是8位,8位可以表示的值为255,从中间127截断为0次幂,126为负1次幂,128为正1次幂,以此类推,64位处理器同理。
怎么避免计算错误
回避策略
根据程序的目的不同微小的误差并不会造成什么问题
把小数转化成整数计算
例如0.1➕100次是不会得到整数10的(计算机二进制不能表示0.1只能无限接近),而将他扩大十倍后,在加一百次,最后除以10就可以得到整数10了
BCD
就是用二进制表示十进制的方法,用4位二进制来表示0~9的一位数字,在财务计算不能出现误差的一定要BCD,或者小数转整数的方法
二进制和十六进制
用十六进制表示二进制,位数缩小四倍,位数变少,看起来跟清晰
表示小数也是4位变一位,位数不够用0补。
对应权位乘以对应的位数的值可以算相对位十进制的值
权数的幂,负一位从负1次幂开始,以此类推。
内存
内存的物理机制
内存IC内部有大量可以储存8进制的地方,通过地址指定这些场所,之后就能今进行数据的读写
内存IC
内存IC中有电源,地址信号,数据信号,控制信号用于输入输出的大量引脚。
内存IC(RAM)的引脚
(RAM)内存IC的引脚VCC和GND是电源(给vcc接入5V,gnd接入0V),A0~A9是地址信号的引脚,D0~D7是数据信号的引脚,RD和WR是控制信号的引脚,引脚电压5V为1,0V为0。
内存IC能存多少数据?
地址引脚有十个可以指定1024个地址,地址引脚是8位1字节,所以1024个字节为1KB所以这个内存IC能存1KB的数据(通常情况下IC会有更多的地址引脚,该内存IC只是方便解释。)
RD和WR
WR为1是写入,RD为1是读出,同时为0就不能进行写入和读出的操作
内存的逻辑模型是楼房
内存模型1024层楼,每层有一字节的数据
数据类型
不同的数据类型站不同层数的楼层,C语言中,char表示一字节的长度,short二字节长度,long四字节长度,分别占的是1层楼,2层楼,4层楼,在C语言中double类型64位8字节是最大的楼层。
中低字节序,根据数据大小从小到大指定低位地址到高位地址。
简单的指针
指针是一种变量,不是数据的值,而是存储着数据的内存的地址。Windows上程序是32位4字节的内存地址,这种情况下指针长度也是32位
浮动主题
例如long类型F指针指向100,使用F时就能读写100~103的地址。指针的数据类型表示一次可以读取的长度
数组是高效使用内存的基础
数组是指多个同样数据类型的数据在内存中连续排列的方式,可以用索引进行读写。
不同类型的数组如short,long会以2个字节,4个字节对内存进群行读写
不同类型数组对应的物理内存不一样,char1字节和楼层完全相等的,short2字节相当于用了两层楼做数组,long4字节相当于用了4层楼做数组。这样就不用逐字节的读写了。能提高效率
通过索引使用数组和内存的物理读写没有什么太大区别
栈,队列以及环形缓冲区
栈,队列
栈和队列都可以不通过指定地址和索引对数组的元素进行读写,需要临时保存计算过程的数据,连接在计算机上的设备或者输入输出的数据时,可以通过这些方法来使用内存。
这里的栈和队列的实体是数组 栈和队列的区别就是一个是后入先出,一个先入先出。 在内存中预留好栈和队列的空间,确立好顺序,就可以不用指定地址和索引了。
用函数对数组读写可能会涉及到索引的管理,单从使用函数的角度上就不用考虑了。
队列和栈的函数基本方法
栈
写入数据为Push(),读出为Pop()
队列
写入为EnQueue(),读出为DeQueue()
栈的LIFO方式
顾名思义,最后的数据Last In会最先出来,最前的数据First Out最后出来,先入后出
为了保护数据的目的,这种内存机制就是栈,有时当我们需要暂时舍弃数据,随后在还原时,会使用栈。
队列的FIFO方式
同上,先入先出
这种方式也称排队,类似于现实生活中的排队当人数过多时排队能起到很好的缓冲作用,排队能起到很好的缓冲机制。程序也是一样的,处理多个程序的数据时,就会用到队列这种处理数据的方法
队列是以环形缓冲区来实现的,一个环形的数组,读取一个之后尾部就空了一个位置,然后头部又写一个位数据,这样头部就一直增加,尾部一直减少,头和尾就连起来了。
链表使元素的追加和删除更容易
在元素的本身附带上下一个元素的索引,就可以实现链表,尾部没有数据用-1来填充
链表删除,增加元素,是通过改变该元素的索引,可以想象成很多很多箭头,当你要删掉一个元素时,你只需要将指向该元素的上一个元素的索引改成该元素的索引,替它指向下一个(这里的索引是指向下一个元素的)。此时该元素已经没有元素指向它了,它就永远指向不了下一个元素了,它从逻辑上来看已经被删除了。 假如你要增加一个元素,你先要确定追加在谁后面,然后改变它的索引指向该元素,该元素的索引要变成它的索引替它指向它原本指向的元素,这样你就在它的后面增加了一个元素。(这里的元素的索引都是指向下一个元素位置)实在不能理解看书
链表的好处,就是不用大规模的移动元素,如果每次增加或删除都要移动成千上万的元素,就是是计算机也吃不消的,而通过链表的指向来追加或删除数据就显得毫不费事了。
二叉查找树
二叉树就是在链表的基础上追加一个分成左右两个方向表现形式,例如大的指右边,小的指左边,就是将一条长长的链表变成一棵二叉树,如果要查找的数据比当前数据大就让它指向右边,小则指左边。两个索引根据逻辑判断去指向下一个元素。二叉树是由链表构造发展出来的表现形式,在追加,删除元素也同样有效。
二叉树能加快找到目标数据的速度
本章节作者寄语
大家要理解为什么要进行这样的处理,另外记住数值才是进行这些处理的关键
内存和磁盘的关系
读入内存才能运行
储存程序方式
程序保存在储存设备中,有序的被读出来。
磁盘的程序无法直接运行,这是因为负责解析和运行程序的CPU,要通过程序计数器指定地址才能读出程序即使CPU可以直接读出磁盘程序,也会因为磁盘读取速度慢而降低运行速度,总之磁盘中的程序读入到内存才能运行
磁盘缓存
磁盘缓存指把从磁盘中读出的数据存储到内存空间中的方式,可以改善磁盘数据访问速度。
这种缓存把低速数据设备保存到高速设备,需要时就可以直接读出了。这种的实例就是web浏览器,把低速的网络数据保存到相对高速的磁盘
虚拟内存把磁盘当内存用
虚拟内存
虚拟内存是指把磁盘的一部分作为假象内存使用。与磁盘缓存相对,缓存实际是内存,虚拟内存实际是磁盘
方法
实现虚拟内存就必须把实际内存的内容,和磁盘的虚拟内存的内容部分进行部分置换,同时运行程序
简单来说,内存把暂时不要运行的部分,放到虚拟内存中去,再从虚拟内存中取出现在要运行的部分,以页或段为单位。
分页式和分段式
Windows采用分页是就是把程序按照一定大小页进行分割,以页为单位在内存和磁盘之间进行置换
Windows在磁盘上提供了虚拟内存的页文件,文件的大小就是虚拟内存的大小,一般是内存的一到两倍。
节约内存的方法
GUI
以图形用户界面为基础的Windows,像Windows这样可以进行可视化操作的方式称为图形用户界面
由于GUI过大,导致内存不够用,虚拟内存又伴随着低速磁盘访问,导致应用运行会变的卡顿。
解决方法
通过DLL文件实现函数共有
DLL就是在程序运行时可以动态加载Library(函数和数据的集合)文件
多个应用可以共用同一个DLL可以达到节约内存的效果。
就比如两个程序有同一个函数,那这个函数就占用了两份内存空间。可以把这个函数设置成DLL就可以实现共享,节省了一份空间。
Windows本身就是多个DLL文件的集合体,在安装应用时,DLL文件也会被追加,应用则会通过DLL文件运行。还有一个好处就是不变更exe文件的情况下,通过升级DLL文件就能更新
调用_stdcall
Windows的DLL都是_stdcall调用的,C里面用的是C特有的调用方式。
C中,栈清理处理是把不需要的数据接收和传递函数参数时使用的内存上的栈区域清理出去。该命令是编译器附加在函数调用方。
例如main()中调用了MyFunc(),按照默认设置栈清理在main()上,同一个程序中可能会多次调用MyFunc(),导致同一处理反复进行,这就造成了内存浪费。
使用_stdcall把栈清理变为被动调用的函数一方进行,只需要进行一次清理。
磁盘的物理结构
磁盘通过把其物理表面划分多个空间来使用的,有扇区方式和可变长方式两种
一种是固定长度空间,另一种是长度可变的空间,Windows的硬盘和软盘都是扇区方式
一个扇区是512字节,软件方面对硬盘进行读写的是扇区的正数倍的簇,磁盘空间越大簇的容量越大。
1簇可以等于512字节,1KB,2KB等等
不管是硬盘软盘,不同的文件不能放在一簇中,无论多小的文件都会占用一簇的空间
虽然有点浪费,但规定如此,如果簇的容量减少,那么硬盘访问的次数就会变多。 硬盘表示扇区还是很有必要的,如果簇的容量过小硬盘的整体容量也会变小,扇区和簇的大小是由处理速度和储存容量觉得的。
压缩数据
文字以字节为单位保存
文件就是字节的集合,在文件中的字节数据都是连续储存的。包括文字,和图片都是字节数据是集合
RLE算法机制
把文件内容用“数据x出现的次数”的形式来表示压缩方法,例如有10个A普通的文件是A有10个重复的,而RlE就把他用Ax10来表示十个A。图形也适用。
缺点
这种比较适合数据相同的图片文件等,不太适合文本压缩,因为如果相同字符连续出现的次数不多时,还会导致文件变大
通过莫斯科编码来看哈夫曼算法
哈夫曼的关键
多次出现的数据用小于8位的字节数来表示,不常用数据用超过8位的字节来表示,实现这一处理压缩内容会很复杂,但压缩率也是很高的。
相当于把一个字节拆开了,来分配编码。
莫斯科编码通过长点,短点和停来表示字符,停表示字符空格,长点短点对应的是1和0,通过对位的数据来查看字符。例如1011表示A
0,1,和停都算做一位
用二叉树实习哈夫曼编码
哈夫曼编码是基于哈夫曼树的,哈夫曼树就是 “实现带权路径长度达到最小”(实现位数最小化) 权相当于出现字符的次数 路径相当于1或0的枝干 总枝干相当于字符编码 枝干数越小字符编码越小所占的位越小 出现次数乘以枝干树等于位数。
哈夫曼算法
为各压缩对象分别构造最佳的编码体系
例如A字母最多,就跟给他分配长度最短的编码。以此类推 用这个方法压缩过的文件中,储存着哈夫曼编码信息
但是这种压缩有着一个问题,那就是字符的之间没有分隔符,1001可能还表示着100和1,用二叉树实习哈夫曼就会有这样的问题了。
二叉树
简单来说就是出现次数越多的数据分支越小,他的位数也就越小。 如果数据种类过多分支也会变多,位数也会变大。由此来实现哈夫曼算法。
哈夫曼算法能大幅提升压缩比例
用枝条连接数据时,从出现频率低的开始,这意味着频率越低,枝条数越多
反正就是你出现频率越大,你的枝条越小。分之,由此得最优的编码号。
读取数据后就会以位为单位进行排查,与哈夫曼树就行比较,然后逐一向下比较,最后到叶。所有能将数据进行区分的原因。
可逆压缩和非可逆
顾名思义,压缩后可逆就是可还原,非可逆就是不能还原,会丢失数据
程序必须是可逆压缩,不然会导致程序无法运行
图像文件
BMP
完全未压缩,显示器,打印机输出的点都是可以直接映射的
JPEG
非可逆压缩,因此还原有一部分是迷糊的
TIFF
可逆压缩,他的文件里包涵很多标签信息,可以选择压缩格式,所有他所占的内存比BMP格式还大
GIF
可逆压缩,由于他的颜色不能超过256色的限制,所以还原会有一点缺失
程序运行的环境
运行环境等于操作系统加硬件
很多软件都要指定的运行环境才运行
同一类型的硬件可以选择安装多种操作系统
例如AT兼容机,可以安装Windows也可以安装Linux。
CPU是运行环境重要的参数,它只能解释自身固有的机器语言
CPU有x86,MIPS,SPARC等他们各自的机器语言不同。 一些软件为了保证正常运行,对CPU也有要求。
机器语言又叫本地代码。C语言等编写的程序,仅仅只是文本文件,我们称之为源代码。只有通过对源代码进行编译才能得到本地代码
Windows应用程序的本地代码,通常是exe和dll文件
Windows克服CPU以为的硬件差异的历史
主要就是Windows解决了软件运行的环境,使程序员不用再注意内存和I/O地址的不同构成了。 只需要对系统就行开发了。
之前计算机机型各不相同(只有CPU相同),但是内存和I/O地址构成都是不同的,因此搭载系统都是不同机型的MS-DOS,x86有同外来设备进行输入输出的I/O地址(I/O地址分配),至于外围设备分配到什么样的地址,则有机型决定
当时购买软件就必须是各机型专用的软件,因为那时的应用软件中存在直接操作硬件的部分,原因是因为MS-DOS功能不完善,提供程序运行速度
随着Windows的广泛使用,这种局面得到改善,只要Windows能运行,用于Windows的软件能再各个机型上运行,在Windows中软件是对Windows发送指令运行的,不过Windows需要为不同机型提供不同的Windows系统
不同系统API不同
应用程序对操作系统传递指令的途径称为API
API通过了任何程序都可以使用的函数组合,因为不同系统的api有差异,因此在移植程序时,要重写用到api部分,像键盘,鼠标,显示器等输入输出功能都是通过api进行的。
CPU种类不同,机器代码就不同,所有需要利用能生成CPU专用本地代码编译器。
程序的本地代码由系统和环境决定
FreeBSD Port
Unix系列操作系统FreeBSSD中有Ports机制能结合当前的硬件环境编译应用的源代码,进而得到可运行的本地代码,应用如果没有源代码就会自动使用FTP连接站点下载代码,FreeBSD上的应用源代码,大部分都是C语言写的,而Unix系统中,都带有C编译器。
简单来说Ports能够克服包涵CPU在内的所有硬件系统差异
利用虚拟机获得其他的操作系统环境
就是在其他硬件机型中安装了其他的操作系统,例如苹果电脑装了Windows系统
提供相同环境的Java虚拟机
Java通过将Java源代码编译后得到字节代码,通过Java虚拟机一边转化为本地代码一边运行
这样实现了同样的字节代码在不同的环境运行,结合操作系统和硬件做成的Java虚拟机可以使同样的字节代码的应用在任何环境下运行了
缺点就是部分字节代码应用,还是不能在Java虚拟机上运行,Java虚拟机多了一到流程运行速度变慢
BIOS和引导
BIOS系统有键盘,磁盘,显卡等基本控制程序外,还有引导程序。
开机后BIOS检测硬件是否正常,没问题后启动引导程序,将硬盘的OS加载到内存中运行,OS再启动软件
从源文件到可执行文件
计算机只能运行本地代码
计算机不能运行源代码,只能运行本地代码,所以源代码都要转化为机器语言
本地代码的内容
Windows的exe使用就是本地代码,把exe内容Dump一下转化为二位16进制。本地代码就是各种数值的罗列,计算机指令也是数值的罗列,这就是本地代码。
编译器负责转化源代码
不同语言的源代码都有自己专属编译器,CPU型号不同能处理的本地代码不同,所以编译器和CPU类型的种类也有关。但是一个好处就是同样的源代码,可以翻译成不同CPU处理的本地代码。
因为编译器也是程序,也要运行环境,例如Windows的C语言编译器,和Linux的C语言编译器。有一种交叉编译器,处理不同CPU所使用的本地代码。
CPU,语言,操作系统
仅靠编译无法得到可执行文件
在源代码中使用函数,源代码都没有这些函数的处理内容。因此在编译过程成exe文件时,这时要进行把函数的处理内容的目标文件和半成品的文件结合,否则就不完整,exe文件无法运行。把多个目标文件结合生成一个exe文件的处理就是链接。在经过链接目标文件的链接器处理后,才会生成exe
启动及库文件
Sample.obj是编译后的目标文件。c0w32.obj文件记述的是所有程序起始位置相结合的处理内容,也叫程序的启动,由BorlandC++提供。
即使程序没有调用函数,也要进行链接,并和启动结合起来。
函数的处理内容的目标文件都在库文件里面,库文件指多个目标文件集成保存在一个文件里,连接器指定库文件后,会抽取目标文件,同其他目标文件结合生成exe文件
标准函数是通过库文件和编译器一起通过的,库文件里面很多标准函数,只需要指定那么几个库文件就可以了。
DLL文件及导入库
Windows以函数的形式为应用通过了很多功能,这些函数就是API。像一些函数的目标文件文件库,本身没有实体,它的实体在Windows的dll文件中,执行时从dll文件调出的函数。这样的库文件叫导入库。
那些有实体功能与exe文件结合的的库文件叫静态链接库。
可执行文件运行时的必要条件
exe文件没有指定函数和和变量的实际内存地址,exe文件给变量和函数的是虚拟内存地址,在程序运行时,虚拟地址会转化为实际地址。链接器会在exe文件开头增加,转化内存地址的必要信息,称为再配置信息。
子主题
程序加载时会生成栈和堆
栈是又来储存函数内部临时使用的变量(局部变量),以及函数调用时所使用的内存区域。
堆是用来存储程序运行时的任意数据及对象的内存领域
加载到内存的程序由用于变量的空间,用于函数的空间,用于栈的空间,用于堆的空间组成。
栈和堆的相似都是在运行内存时申请分配的,不同的地方在于栈会自动释放内存,而堆不会,需要提供函数进行释放,如果不释放,它会一直残留。这种现象称之为内存泄露。可能会使电脑内存不足导致宕机。
Q&A
编译器在运行前对所有程序进行解释,而解释器一边运行一边解释
分割编译指将程序分成多个源代码,通过链接成一个exe文件,便于管理
有的编译器可以提供Build生成exe文件,Build指的是连续执行编译和链接
DDL好处多个程序公用,节省内存,修改函数时不用重新链接(静态链接)使用这个函数的程序了。
不链接导入库,可以使用API调用dll文件函数。
叠加链接指将不会同时执行的函数,加载到同一个地址,通过叠加链接器可以实现,能节省内存
垃圾回收指把不需要的堆内存的数据进行清理,C语言用的是free(),以C语言开发出来的其他语言中,程序运行会自动进行垃圾回收,避免忘记释放内存
操作系统和应用的关系
操作系统历史
早期没有操作系统,人们开发了有加载和运行功能的监控程序,慢慢的人们将一系列的输出输入程序追加到监控程序中,早期的操作系统也就完成啦。因此操作系统是多个程序的集合
要意识到操作系统的存在
有了操作系统后,程序员和硬件就被系统隔开了。不过应该要掌握基本的硬件知识,并借助操作系统进行抽象化,可以提高效率。
应用可执行文件,计算机的CPU可以直接解释并运行的本地代码,不过这些代码无法直接控制计算机的时钟IC及显示器的I/O等硬件的。在操作系统的运行环境下,通过操作系统间接控制硬件,基本上程序都是面相系统的而不是面相硬件,系统收到指令先解释,然后在对硬件进行控制
系统调用和高级编程语言的移植形
操作系统硬件功能由各种小函数提供,这些调用这些函数的行为叫系统调用。而一般语言函数的内部都有系统调用,系统调用在函数的内部执行。在不同操作系统下能使用相同的代码,只需要修改系统调用就行了(移植性)。
操作系统和高级编程语言使硬件抽象化
文件是操作系统对磁盘媒介空间的抽象化,相当于扇区抽象成了文件。
fopen()函数的返回值,该值称为文件指针。打开文件后系统会自动分配管理该文件读写的内存空间,通过指定文件指针对文件进行操作。
简单来说就是文件指针,指向文件,通过指定文件指针对文件进行编辑。
Windows系统特点
对程序员有意义
32位系统
处理不同位数的数据类型时,高位点数据使内存和磁盘的开销较大,低位反之
API函数集提供系统调用
API是联系应用程序和操作系统之间的接口,32位的API中,各参数及返回值都是32位。API进行系统调用,API通过多个DLL文件提供,DLL通过使用API调用或其动态调用请求某些服务,API实体都是C编写的。
GUI用户界面
用的时候是天堂,做的时候是地狱,之前的ms-dos应用处理流程,是程序员决定,而GUI决定流程,因此必须要写出任何顺序都能运行的应用,这才是GUI的难点
WYSIWYG实习打印输出
WYSIWYG指通过显示器显示输出和打印机打印输出,通Windows的WYSIWYG功能,实习同一个程序实习打印和显示两操作。
多任务功能
通过时钟分割技术实现多任务,一个任务执行另一个待机,相互转换,转换时间有时钟分割确定
通过网络功能及数据库功能
网络功能由标准功能通过,数据库也可能是后面追加的,被称为中间件,不是软件,和系统相近被称为系统软件。
即插即用
外部设备连接,系统会自动安装和设定用来控制该设备的设备驱动,往往在购买时里面通常收录着设备驱动程序(DLL与API)
通过汇编了解程序实际构造
汇编与本地代码
汇编与本地代码对应,汇编转本地通过汇编器,本地转汇编为反汇编,C语言不能反汇编因为它与本地代码不是一一对应的。
通过编译器输出的汇编语言的源代码
本地代码的伪指令
汇编的源代码是转换成本地代码的指令和针对汇编器的伪指令构成。
汇编语言语法是“操作码”加“操作数”
相当于1+1,1为操作数,+为操作码。
运行流程是,本地加载到内存,内存有本地代码的指令和数据后,传给CPU,然后我就CPU的寄存器进行处理
mov指令
两个操作数“储存地”和“读出源”
对栈进行push和pop
push是入栈pop是出栈
函数调用机制
详细看书
确保全局变量的内存空间
子主题