Skip to content

Latest commit

 

History

History
617 lines (309 loc) · 33.7 KB

程序是怎样跑起来的.md

File metadata and controls

617 lines (309 loc) · 33.7 KB

程序是怎样跑起来的

矢泽久雄 - 计算机榜-编程设计

日文版重印41次,“计算机组成原理”图解趣味版,蹲马桶就能看懂的编程基础知识!本书适合:菜鸟程序员入门进阶;中级程序员查漏补缺;高手程序员向家人(女友、老妈等)普及计算机知识。如何向小学生讲解CPU和二进制?如何向中学生讲解内存和磁盘?如何向女高中生讲解操作系统的原理?如何向老奶奶说明显示器和电视的不同?如果你完全没有思路,就应该读一读这本书。以图配文,深入讲解编程基础知识;语言通俗,即使是文科生也能看得懂。

内容提要

二进制、内存、数据压缩、源文件和可执行文件、操作系统和应用程序的关系、汇编语言、硬件控制方法等内容 c:133

前言

加载到内存中的机器语言程序,由CPU进行解析和运行,进而计算机系统整体的控制和数据运算也开始运行。了解了程序的运行机制后,就能找到编写源程序的方法。 c:520

无论任何事情,了解其本质非常重要。只有了解了本质才能提高利用效率。这样一来,即使有新技术出现,也能很容易地理解并掌握。 c:636

程序是怎样跑起来的——本书中涉及的主要关键词

读完本书,你就会了解从双击程序图标开始到程序运行的整个机制。 c:42

本书的结构

通过向他人介绍,可以对自己的掌握程度进行充分的验证。各位读者在阅读时也不妨考虑一下:如果是你,你会怎样介绍呢? c:132

第1章 对程序员来说CPU是什么

程序是指令和数据的组合体 c:390

程序员还需要理解CPU是如何运行的,特别是要弄清楚负责保存指令和数据的寄存器的机制。 c:520

1.1 CPU的内部结构解析

了解程序的运行流程是掌握程序运行机制的基础和前提 c:146

1.2 CPU是寄存器的集合体

程序是把寄存器作为对象来描述的。 c:1066

数据分为“用于运算的数值”和“表示内存地址的数值”两种 c:702

1.3 决定程序流程的程序计数器

决定程序流程的程序计数器 c:63

程序计数器决定着程序的流程。 c:382

1.4 条件分支和循环机制

程序的流程分为顺序执行、条件分支和循环三种。顺序执行是指按照地址内容的顺序执行指令。条件分支是指根据条件执行任意地址的指令。循环是指重复执行同一地址的指令。 c:468

程序中的比较指令,就是在CPU内部做减法运算。 c:616

1.5 函数的调用机制

函数调用处理也是通过把程序计数器的值设定成函数的存储地址来实现的。 c:550

函数调用使用的是call指令,而不是跳转指令。在将函数的入口地址设定到程序计数器之前,call指令会把调用函数后要执行的指令地址存储在名为栈[插图]的主存内。函数处理完毕后,再通过函数的出口来执行return命令。return命令的功能是把保存在栈中的地址设定到程序计数器中。 c:886

1.6 通过地址和索引实现数组

基址寄存器和变址寄存器。通过这两个寄存器,我们可以对主内存上特定的内存区域进行划分,从而实现类似于数组注1的操作。 c:305

1.7 CPU的处理其实很简单

机器语言指令的主要类型和功能 c:195

1位代表二进制数的一个字节位 c:68

第2章 数据是用二进制数表示的

XOR运算只反转与1相对应的位。NOT运算是反转所有的位 c:113

也就是说,只要掌握了使用二进制数来表示信息的方法及其运算机制,也就自然能够了解程序的运行机制了。 c:117

2.1 用二进制数表示计算机信息的原因

IC的所有引脚,只有直流电压0V或5V[插图]两个状态。也就是说,IC的一个引脚,只能表示两个状态。 c:458

2.2 什么是二进制数

二进制数的值转换成十进制数的值,只需将二进制数的各数位的值和位权相乘,然后将相乘的结果相加即可 c:177

2.3 移位运算和乘除运算的关系

和十进制数一样,四则运算同样也可以使用在二进制数中,只要注意逢2进位即可 c:70

移位运算指的是将二进制数值的各数位进行左右移位(shift=移位)的运算。 c:283

移位操作使最高位或最低位溢出的数字,直接丢弃就可以了。 c:319

十进制数左移后会变成原来的10倍、100倍、1000倍……同样,二进制数左移后就会变成原来的2倍、4倍、8倍……反之,二进制数右移后则会变成原来的1/2、1/4、1/8…… c:324

2.4 便于计算机处理的“补数”

要想区分什么时候补0什么时候补1,只要掌握了用二进制数表示负数的方法即可 c:155

为此,在表示负数时就需要使用“二进制的补数”。补数就是用正数来表示负数 c:541

2.5 逻辑右移和算术右移的区别

当二进制数的值表示图形模式而非数值时,移位后需要在最高位补0。类似于霓虹灯往右滚动的效果。这就称为逻辑右移 c:443

2.6 掌握逻辑运算的窍门

计算机能处理的运算,大体可分为算术运算和逻辑运算。算术运算是指加减乘除四则运算。逻辑运算是指对二进制数各数字位的0和1分别进行处理的运算,包括逻辑非(NOT运算)、逻辑与(AND运算)、逻辑或(OR运算)和逻辑异或(XOR运算[插图])四种。 c:478

逻辑运算的运算对象不是数值,因此不会出现进位的情况。看起来好像有些麻烦,总之就是不要将它作为数值来考虑。 c:182

第3章 计算机进行小数运算时出错的原因

浮点数是指把小数用“符号 尾数×基数的指数次幂”这种形式来表示。 c:117

3.2 用二进制数表示小数

不过,使用二进制数来表示整数和小数的方法却有很大的不同。 c:81

3.3 计算机运算出错的原因

计算机之所以会出现运算错误,是因为“有一些十进制数的小数无法转换成二进制数”。 c:550

3.4 什么是浮点数

双精度浮点数类型用64位、单精度浮点数类型用32位来表示全体小数 c:381

浮点数是指用符号、尾数、基数和指数这四部分来表示的小数 c:537

3.5 正则表达式和EXCESS系统

十进制数的浮点数应该遵循“小数点前面是0,小数点后面第1位不能是0”这样的规则 c:258

EXCESS系统表现是指,通过将指数部分表示范围的中间值设为0,使得负数不需要用符号来表示。 c:263

3.7 如何避免计算机计算出错

另一个策略是把小数转换成整数来计算。计算机在进行小数计算时可能会出错,但进行整数计算(只要不超过可处理的数值范围)时一定不会出现问题。 c:216

在涉及财务计算等不允许出现误差的情况下,一定要将小数转换成整数或者采用BCD方法,以确保最终得到准确的数值。 c:291

3.8 二进制数和十六进制数

在C语言程序中,只需在数值的开头加上0x(0和x)就可以表示十六进制数。 c:175

第4章 熟练使用有棱有角的内存

物理内存是以字节为单位进行数据存储的。 c:174

计算机是进行数据处理的设备,而程序表示的就是处理顺序和数据结构 c:267

4.1 内存的物理机制很简单

内存IC中有电源、地址信号、数据信号、控制信号等用于输入输出的大量引脚(IC的引脚),通过为其指定地址(address),来进行数据的读写。 c:420

4.2 内存的逻辑模型是楼房

通过使用变量,即便不指定物理地址,也可以在程序中对内存进行读写。这是因为,在程序运行时,Windows等操作系统会自动决定变量的物理地址。 c:216

4.3 简单的指针

指针也是一种变量,它所表示的不是数据的值,而是存储着数据的内存的地址。 c:559

假设d、e、f的值都是100。在这种情况下,使用d时就能够从编号100的地址中读写1个字节的数据,使用e时就是2个字节(100地址和101地址)的数据,使用f时就是4个字节(100地址~103地址)的数据。 c:127

指针的数据类型表示一次可以读写的长度 c:120

4.4 数组是高效使用内存的基础

数组是指多个同样数据类型的数据在内存中连续排列的形式。作为数组元素的各个数据会通过连续的编号被区分开来,这个编号称为索引(index)。 c:433

数组的定义中所指定的数据类型,也表示一次能够读写的内存大小。char类型的数组以1个字节为单位对内存进行读写,而short类型和long类型的数组则分别以2个字节、4个字节为单位对内存进行读写。 c:219

之所以说数组是内存的使用方法的基础,是因为数组和内存的物理构造是一样的。特别是1字节类型的数组,它和内存的物理构造完全一致。 c:292

4.5 栈、队列以及环形缓冲区

栈用的是LIFO(Last Input First Out,后入先出)方式,而队列用的则是FIFO(First Input First Out,先入先出)方式。 c:264

这种机制体现在内存上,就是栈。当我们需要暂时舍弃当前的数据,随后再原貌还原时,会使用栈。 c:233

队列一般是以环状缓冲区(ring buffer)的方式来实现的 c:328

4.6 链表使元素的追加和删除更容易

在数组的各个元素中,除了数据的值之外,通过为其附带上下一个元素的索引,即可实现链表。数据的值和下一个元素的索引组合在一起,就构成了数组的一个元素。 c:272

如果不使用链表数组,那么中途删除或追加元素时,其后的元素就必须要全部移动。 c:141

4.7 二叉查找树使数据搜索更有效

二叉查找树[插图]是指在链表的基础上往数组中追加元素时,考虑到数据的大小关系,将其分成左右两个方向的表现形式。 c:265

第5章 内存和磁盘的亲密关系

磁盘缓存是指,把从磁盘中读出的数据存储在内存中,当该数据再次被读取时,不是从磁盘而是直接从内存中高速读出。 c:184

利用电流来实现存储的内存,同利用磁效应来实现存储的磁盘,还是有差异的。而从存储容量来看,内存是高速高价,而磁盘则是低速廉价。 c:307

5.1 不读入内存就无法运行

磁盘中存储的程序,必须要加载到内存后才能运行。在磁盘中保存的原始程序是无法直接运行的。这是因为,负责解析和运行程序内容的CPU,需要通过内部程序计数器来指定内存地址,然后才能读出程序[插图]。 c:397

5.2 磁盘缓存加快了磁盘访问速度

磁盘缓存指的是把从磁盘中读出的数据存储到内存空间中的方式。 c:389

5.3 虚拟内存把磁盘作为部分内存来使用

虚拟内存是指把磁盘的一部分作为假想的内存来使用。这与磁盘缓存是假想的磁盘(实际上是内存)相对,虚拟内存是假想的内存(实际上是磁盘)。 c:480

虚拟内存的方法有分页式和分段式[插图]两种。 c:431

5.4 节约内存的编程方法

以图形用户界面(GUI, Graphical User Interface)[插图]为基础的Windows,可以说是一个巨大的操作系统。 c:75

由于使用虚拟内存时发生的Page In和Page Out往往伴随着低速的磁盘访问,因此在这个过程中应用的运行会变得迟钝起来 c:189

在C语言中,函数的返回值,是通过寄存器而非栈来返回的。 c:262

5.5 磁盘的物理结构

磁盘是通过把其物理表面划分成多个空间来使用的。划分的方式有扇区方式和可变长方式两种,前者是指将磁盘划分为固定长度的空间,后者则是指把磁盘划分为长度可变的空间。 c:386

磁盘的数据保存是以簇为单位来进行 c:176

扇区和簇的大小,是由处理速度和存储容量的平衡来决定的。 c:189

第6章 亲自尝试压缩数据

半角英文数字是用1个字节来表示的,汉字等全角字符是用2个字节来表示的。 c:125

6.1 文件以字节为单位保存

在任何情况下,文件中的字节数据都是连续存储的,大家一定要认识到这一点 c:406

6.2 RLE算法的机制

由于半角字母中,1个字符是作为1个字节的数据被保存在文件中的。 c:79

这时,大家是不是会采取将文件的内容用“字符×重复次数”这样的表现方式来压缩呢。 c:29

把文件内容用“数据×重复次数”的形式来表示的压缩方法称为RLE(Run Length Encoding,行程长度编码)算法(图6-2)。 c:381

6.3 RLE算法的缺点

虽然针对相同数据经常连续出现的图像、文件等,RLE算法可以发挥不错的效果,但它并不适合文本文件的压缩 c:154

6.4 通过莫尔斯编码来看哈夫曼算法的基础

哈夫曼算法的关键就在于“多次出现的数据用小于8位的字节数来表示,不常用的数据则可以用超过8位的字节数来表示” c:290

莫尔斯编码不是通过语言,而是通过“嗒嘀嗒嘀”这些长点和短点的组合来传递文本信息的。想 c:110

6.5 用二叉树实现哈夫曼编码

莫尔斯编码是根据日常文本中各字符的出现频率来决定表示各字符的编码的数据长度的 c:53

哈夫曼算法是指,为各压缩对象文件分别构造最佳的编码体系,并以该编码体系为基础来进行压缩。 c:321

而在哈夫曼算法中,通过借助哈夫曼树构造编码体系,即使在不使用字符区分符号的情况下,也可以构建能够明确进行区分的编码体系。也就是说,利用哈夫曼树后,就算表示各字符的数据位数不同,也能够做成可以明确区分的编码。 c:153

6.6 哈夫曼算法能够大幅提升压缩比率

使用哈夫曼树后,出现频率越高的数据所占用的数据位数就越少,而且数据的区分也可以很清晰地实现。 c:89

而从用哈夫曼算法压缩过的文件中读取数据后,就会以位为单位对该数据进行排查,并与哈夫曼树进行比较看是否到达了目标编码 c:110

6.7 可逆压缩和非可逆压缩

Windows的标准图像数据形式为BMP[插图],是完全未压缩的。由于显示器及打印机输出的bit(点)是可以直接映射(mapping)的,因此便有了BMP=bitmap这一名称。 c:222

COLUMN如果是你,你会怎样介绍?

游戏机是一边把CD中存储的软件部分复制入内存,一边运行游戏的。 c:55

盒式卡带的情况下,可以将游戏机主机的内存完整置换,所以不需要往内存中复制数据。只有磁盘才必须把数据复制到内存中。 c:59

第7章 程序是在何种环境中运行的

不同的硬件种类需要不同的操作系统。 c:36

运行环境不同指的是什么呢?为什么运行环境不同,应用就无法运行呢? c:24

7.1 运行环境=操作系统 + 硬件

运行环境=操作系统 + 硬件 c:179

操作系统和硬件决定了程序的运行环境。 c:242

机器语言的程序称为本地代码(native code)。程序员用C语言等编写的程序,在编写阶段仅仅是文本文件。文本文件(排除文字编码的问题)在任何环境下都能显示和编辑。我们称之为源代码。通过对源代码进行编译,就可以得到本地代码。 c:348

7.2 Windows克服了CPU以外的硬件差异

计算机的硬件并不仅仅是由CPU构成的,还包括用于存储程序指令和数据的内存,以及通过I/O连接的键盘、显示器、硬盘、打印机等外围设备。 c:101

Windows操作系统对克服这些硬件构成的差异做出了很大贡献。 c:25

MS-DOS应用大多都是不经过操作系统而直接控制硬件的,而Windows应用则基本上都由Windows来完成对硬件的控制 c:148

7.3 不同操作系统的API不同

应用程序向操作系统传递指令的途径称为API(Application Programming Interface) c:412

7.4 FreeBSD Port帮你轻松使用源代码

Unix系列操作系统FreeBSD中,存在一种名为Ports的机制。该机制能够结合当前运行的硬件环境来编译应用的源代码,进而得到可以运行的本地代码系统。 c:243

Ports这个术语,表示的是porting(移植)的意思。而根据不同的运行环境来重新调整程序,一般也称为“移植”。 c:144

7.5 利用虚拟机获得其他操作系统环境

即使不通过移植,也可以使用别的方法来运行其他操作系统的应用。这里我们要介绍的方法就是利用虚拟机软件。 c:64

Virtual PC for MAC可以使Macintosh这一硬件变得同AT兼容机一样,从而能在该硬件上安装Windows。 c:38

7.6 提供相同运行环境的Java虚拟机

同其他编程语言相同,Java也是将Java语法记述的源代码编译后运行。不过,编译后生成的并不是特定CPU使用的本地代码,而是名为字节代码的程序。字节代码的运行环境就称为Java虚拟机(JavaVM, Java Virtual Machine)。Java虚拟机是一边把Java字节代码逐一转换成本地代码一边运行的。 c:299

从操作系统方面来看,Java虚拟机是一个应用,而从Java应用方面来看,Java虚拟机就是运行环境。 c:188

7.7 BIOS和引导

引导程序是存储在启动驱动器起始区域的小程序。 c:210

第8章 从源文件到可执行文件

通过对源文件进行编译,得到目标文件。例如,C语言中,将Sample1.c这个源文件编译后,就会得到Sample1.obj这个目标文件。目标文件的内容是本地代码。 c:83

源代码完成后,就可以编译生成可执行文件了。负责实现该功能的是编译器 c:65

8.1 计算机只能运行本地代码

计算机只能运行本地代码 c:28

用某种编程语言编写的程序就称为源代码[插图],保存源代码的文件称为源文件。 c:142

8.2 本地代码的内容

也正是因为如此,才有了用人类容易理解的C语言等编程语言来编写源代码,然后再将源代码转换成本地代码这一方法。 c:44

。Dump是指把文件的内容,每个字节用2位十六进制数来表示的方式。 c:362

8.3 编译器负责转换源代码

读入的源代码还要经过语法解析、句法解析、语义解析等,才能生成本地代码。 c:352

一种交叉编译器,它生成的是和运行环境中的CPU不同的CPU所使用的本地代码 c:138

8.4 仅靠编译是无法得到可执行文件的

为了得到可以运行的EXE文件,编译之后还需要进行“链接”处理。 c:233

编译后生成的不是EXE文件,而是扩展名为“.obj”的目标文件 c:155

把多个目标文件结合,生成1个EXE文件的处理就是链接,运行连接的程序就称为链接器(linkage editor或连结器) c:474

8.5 启动及库文件

c0w32.obj这个目标文件记述的是同所有程序起始位置相结合的处理内容,称为程序的启动。 c:243

库文件指的是把多个目标文件集成保存到一个文件中的形式。链接器指定库文件后,就会从中把需要的目标文件抽取出来,并同其他目标文件结合生成EXE文件。 c:450

sprintf()等函数,不是通过源代码形式而是通过库文件形式和编译器一起提供的。这样的函数称为标准函数。之所以使用库文件,是为了简化为链接器的参数指定多个目标文件这一过程。 c:323

8.6 DLL文件及导入库

Windows中,API的目标文件,并不是存储在通常的库文件中,而是存储在名为DLL(Dynamic Link Library)文件的特殊库文件中。 c:330

8.7 可执行文件运行时的必要条件

那就是EXE文件中给变量及函数分配了虚拟的内存地址。在程序运行时,虚拟的内存地址会转换成实际的内存地址。链接器会在EXE文件的开头,追加转换内存地址所需的必要信息。这个信息称为再配置信息。 c:500

8.8 程序加载时会生成栈和堆

栈是用来存储函数内部临时使用的变量(局部变量),以及函数调用时所用的参数的内存区域。堆是用来存储程序运行时的任意数据及对象的内存领域 c:552

8.9 有点难度的Q&A

编译器是在运行前对所有源代码进行解释处理的。而解释器则是在运行时对源代码的内容一行一行地进行解释处理的。 c:217

第9章 操作系统和应用的关系

监控程序也可以说是操作系统的原型。 c:101

程序员的工作就是编写各种各样的应用来提高业务效率。 c:103

9.1 操作系统功能的历史

有人开发出了仅具有加载和运行功能的监控程序,这就是操作系统的原型。 c:326

操作系统本身并不是单独的程序,而是多个程序的集合体 c:155

9.2 要意识到操作系统的存在

要想成为一个全面的程序员,有一点需要清楚的是,掌握基本的硬件知识,并借助操作系统进行抽象化,可以大大提高编程效率。 c:198

在操作系统这个运行环境下,应用并不是直接控制硬件,而是通过操作系统来间接控制硬件的。变量定义中涉及的内存的申请分配,以及time()和printf()这些函数的运行结果,都不是面向硬件而是面向操作系统的。操作系统收到应用发出的指令后,首先会对该指令进行解释,然后会对时钟IC(实时时钟[插图])和显示器用的I/O进行控制。 c:128

9.3 系统调用和高级编程语言的移植性

操作系统的硬件控制功能,通常是通过一些小的函数集合体的形式来提供的。这些函数及调用函数的行为统称为系统调用(system call),也就是应用对操作系统(system)的功能进行调用(call)的意思。 c:278

高级编程语言的机制就是,使用独自的函数名,然后再在编译时将其转换成相应操作系统的系统调用(也有可能是多个系统调用的组合) c:289

9.4 操作系统和高级编程语言使硬件抽象化

这是因为操作系统和高级编程语言能够使硬件抽象化。这是个非常了不起的处理。 c:143

文件是操作系统对磁盘媒介空间的抽象化。 c:265

磁盘媒介的读写采用了文件这个概念,将整个流程抽象化成了打开文件用的fopen()、写入文件用的fputs()、关闭文件用的fclose() c:95

9.5 Windows操作系统的特征

Windows操作系统的主要特征如下所示。(1)32位操作系统(也有64位版本)(2)通过API函数集来提供系统调用(3)提供采用了图形用户界面的用户界面(4)通过WYSIWYG[插图]实现打印输出(5)提供多任务功能(6)提供网络功能及数据库功能(7)通过即插即用实现设备驱动的自动设定 c:110

这里的32位表示的是处理效率最高的数据大小 c:111

Windows是通过名为API的函数集来提供系统调用的。API是联系应用程序和操作系统之间的接口。所以称为API(Application Programming Interface,应用程序接口)。 c:133

API通过多个DLL文件来提供。各API的实体都是用C语言编写的函数。 c:210

GUI,用的时候是天堂,做的时候是地狱 c:54

在像MS-DOS这种没有使用GUI的操作系统中,应用的处理流程由程序员决定,用户按照定好的流程来进行操作即可。与此相反,采用GUI的操作系统中运行的应用,则是由用户决定处理流程的。因此,程序员就必须要制作出在任何操作顺序下都能运行的应用 c:100

多任务指的是同时运行多个程序的功能。Windows是通过时钟分割技术来实现多任务功能的。 c:283

程序是操作系统、中间件、应用等所有软件的统称 c:184

能够直接用本地代码编写程序的人,实际上并不多见。大家的普遍做法都是使用汇编语言来代替本地代码。 c:40

第10章 通过汇编语言了解程序的实际构成

汇编语言是通过利用助记符来记述程序的。 c:74

10.1 汇编语言和本地代码是一一对应的

这些缩写称为助记符,使用助记符的编程语言称为汇编语言。 c:342

10.2 通过编译器输出汇编语言的源代码

大部分C语言编译器,都可以把利用C语言编写的源代码转换成汇编语言的源代码,而不是本地代码 c:130

汇编语言源文件的扩展名,通常用“.asm”来表示 c:103

10.3 不会转换成本地代码的伪指令

汇编语言的源代码,是由转换成本地代码的指令(后面讲述的操作码)和针对汇编器的伪指令构成的。 c:255

由伪指令segment和ends围起来的部分,是给构成程序的命令和数据的集合体加上一个名字而得到的,称为段定义 c:227

即使在源代码中指令和数据是混杂编写的,经过编译或者汇编后,也会转换成段定义划分整齐的本地代码 c:85

伪指令proc和endp围起来的部分,表示的是过程(procedure)的范围。在汇编语言中,这种相当于C语言的函数的形式称为过程。 c:153

10.4 汇编语言的语法是“操作码+操作数”

汇编语言指令的语法结构是操作码+操作数[插图](也存在只有操作码没有操作数的指令)。 c:276

操作数中指定了寄存器名、内存地址、常数等 c:82

寄存器是CPU中的存储区域。不过,寄存器并不仅仅具有存储指令和数据的功能,也有运算功能 c:131

10.5 最常用的mov指令

如果指定了没有用方括号围起来的内容,就表示对该值进行处理;如果指定了用方括号围起来的内容,方括号中的值则会被解释为内存地址,然后就会对该内存地址对应的值进行读写操作。 c:167

dword ptr(double word pointer)表示的是从指定内存地址读出4字节的数据 c:134

10.6 对栈进行push和pop

就如该名称所表示的那样,数据在存储时是从内存的下层(大的地址编号)逐渐往上层(小的地址编号)累积,读出时则是按照从上往下的顺利进行(图10-3)的 c:124

10.7 函数调用机制

函数调用是栈发挥大作用的场合。 c:133

在汇编语言中,函数名表示的是函数所在的内存地址 c:114

最优化功能是编译器在本地代码上费尽功夫实现的,其目的是让编译后的程序运行速度更快、文件更小。 c:120

10.8 函数内部的处理

(2)中把负责管理栈地址的esp寄存器的值赋值到了ebp寄存器中。这是因为,在mov指令中方括号内的参数,是不允许指定esp寄存器的。因此,这里就采用了不直接通过esp,而是用ebp寄存器来读写栈内容的方法。 c:62

函数的参数是通过栈来传递,返回值是通过寄存器来返回的 c:386

10.9 始终确保全局变量用的内存空间

C语言中,在函数外部定义的变量称为全局变量,在函数内部定义的变量称为局部变量 c:188

10.10 临时确保局部变量用的内存空间

局部变量是临时保存在寄存器和栈中的 c:237

仅仅对局部变量进行定义是不够的,只有在给局部变量赋值时,才会被分配到寄存器的内存区域。 c:56

例如,在函数入口处为变量申请分配栈的内存空间的话,就必须在函数出口处进行释放。否则,经过多次调用函数后,栈的内存空间就会被用光了。 c:86

10.11 循环处理的实现方法

在汇编语言的源代码中,循环是通过比较指令(cmp)和跳转指令(jl)来实现的。 c:96

言中比较指令的结果,会存储在CPU的标志寄存器中。不过 c:60

汇编语言是对CPU的实际运行进行直接描述的低级编程语言,C语言是用与人类的感觉相近的表现来描述的高级编程语言 c:99

10.13 了解程序运行方式的必要性

如果只是看counter *=2;的话,就会以为counter的数值被直接扩大为了原来的2倍。然而,实际上执行的却是“把counter的数值读入eax寄存器”“将eax寄存器的数值变成原来的2倍”“把eax寄存器的数值写入counter”这3个处理。 c:99

在多线程处理中,用汇编语言记述的代码每运行1行,处理都有可能切换到其他线程(函数)中。 c:108

为了避免该bug,我们可以采用以函数或C语言源代码的行为单位来禁止线程切换的锁定方法。通过锁定,在特定范围内的处理完成之前,处理不会被切换到其他函数中。 c:146

如果大家会使用C语言的话,希望大家对C语言的各种语法所对应的汇编语言都一一确认一下。最好能编写一些简短的程序来进行反复的测试。笔者自身也是通过进行这些尝试才使自己的编程技能有了大幅提高的。 c:53

第11章 硬件控制方法

DMA指的是,不经过CPU中介处理,外围设备直接同计算机的主内存进行数据传输。 c:148

控制CPU,只需把编译器或汇编器生成的本地代码加载到主内存并运行就可以了 c:75

11.1 应用和硬件无关?

利用操作系统提供的系统调用功能就可以实现对硬件的控制。在Windows中,系统调用称为API(图11-1)。各API就是应用调用的函数。这些函数的实体被存储在DLL文件中。 c:210

Windows做了什么呢?从结果来看,Windows直接控制了作为硬件的显示器。但Windows本身也是软件,由此可见,Windows应该向CPU传递了某些指令,从而通过软件控制了硬件。 c:64

11.2 支撑硬件输入输出的IN指令和OUT指令

IN指令通过指定端口号的端口输入数据,并将其存储在CPU内部的寄存器中。OUT指令则是把CPU寄存器中存储的数据,输出到指定端口号的端口。 c:284

11.3 编写测试用的输入输出程序

虽然蜂鸣器内置在计算机内部,但其本身也是外围设备的一种。因为就算是把蜂鸣器取出,对计算机主机也不会有什么影响。 c:37

在大部分C语言的处理(编译器的种类)中,只要使用_asm{和}括起来,就可以在其中记述助记符。也就是说,这样就可以编写C语言和汇编语言混合的源代码 c:109

11.4 外围设备的中断请求

IRQ是用来暂停当前正在运行的程序,并跳转到其他程序运行的必要机制。该机制称为中断处理 c:312

实施中断请求的是连接外围设备的I/O控制器,负责实施中断处理程序的是CPU。 c:226

11.5 用中断来实现实时处理

按照顺序调查多个外围设备的状态称为轮询 c:177

11.6 DMA可以实现短时间内传送大量数据

DMA是指在不通过CPU的情况下,外围设备直接和主内存进行数据传送。 c:347

I/O端口号、IRQ、DMA通道可以说是识别外围设备的3点组合 c:166

11.7 文字及图片的显示机制

显示器中显示的信息一直存储在某内存中。该内存称为VRAM(Video RAM)。在程序中,只要往VRAM中写入数据,该数据就会在显示器中显示出来。实现该功能的程序,是由操作系统或BIOS提供,并借助中断来进行处理的。 c:301

虽然计算机领域的新技术在不断涌现,但计算机能处理的事情,始终只是对输入的数据进行运算,并把结果输出,这一点是不会发生任何变化的 c:187

第12章 让计算机“思考”

伪随机数同真正的随机数不同,具有周期性。 c:70

12.1 作为“工具”的程序和为了“思考”的程序

控制就是指CPU和各种设备之间配合进行数据的输入输出处理 c:173

另外一个使用目的是用程序来代替执行人类的思考过程。 c:98

12.4 程序生成随机数的方法

随机数也是用程序来表示人类的直觉及念头的一种方法 c:56

12.5 活用记忆功能以达到更接近人类的判断

人类的日常判断通常是根据直觉和经验做出的。直觉并不仅仅是简单的任意思考,通常还带有一些个人的思维习惯。 c:45

C语言的特点

C语言虽是高级编程语言,但它也具备了能够和汇编语言相媲美的低层处理(内存操作及位操作)功能。 c:78

变量和函数

在C语言中,数据用变量来表示,处理用函数来表示 c:137

数据类型

计算机中预先被定义过的位数和精度称为数据类型。 c:157

标准函数库

函数包括程序员自己编写的函数以及系统提供的函数。其中,后者通常称为标准函数库。标准函数库是指具有可被各种程序使用的通用功能的函数。 c:140

函数的括号中,除变量以外,也可以放置通过文字串、数值等指定的数据信息,这些统称为参数。被作为函数的处理结果而返回的数值称为返回值。利用函数称为函数调用。 c:101

计算机的基本操作大体可以划分为“输入数据”“处理数据”“输出数据”三块。 c:54

函数调用

main是程序启动时最初运行的函数。在由多个函数构成的程序中,程序启动时运行main函数,并在main函数中调用其他函数,然后该函数又调用其他函数……,像这样,所需要的函数会被一个接一个地调用。 c:75

局部变量和全局变量

在函数模块中定义的变量,只能在该函数中使用。这样的变量就称为局部变量。 c:69

变量也可以在函数模块外进行定义(虽然函数处理必须要在函数的模块中进行,但变量是可以在模块外进行定义的),该变量称为全局变量。 c:70

其他语法结构

C语言的语法结构是ANSI(American National Standard Institute,美国国家标准协会)制定的。 c:44

结语

记得有“自己吓唬自己是最可怕的事情”这样的说法。如果总是想一些令自己担心恐惧的事情,枯萎的花朵都能被看成幽灵,这句话说的就是这样的心理。 c:63