程序是怎样跑起来的

矢泽久雄

内容提要

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

前言

  • 加载到内存中的机器语言程序,由CPU进行解析和运行,进而计算机系统整体的控制和数据运算也开始运行。了解了程序的运行机制后,就能找到编写源程序的方法。
  • 无论任何事情,了解其本质非常重要。只有了解了本质才能提高利用效率。这样一来,即使有新技术出现,也能很容易地理解并掌握。

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

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

本书的结构

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

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

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

1.1 CPU的内部结构解析

  • 了解程序的运行流程是掌握程序运行机制的基础和前提
  • CPU[插图]所负责的就是解释和运行最终转换成机器语言的程序内容。

1.2 CPU是寄存器的集合体

  • 程序是把寄存器作为对象来描述的。
  • 内存的存储场所通过地址编号来区分,而寄存器的种类则通过名字来区分。
  • 数据分为“用于运算的数值”和“表示内存地址的数值”两种

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

  • 决定程序流程的程序计数器
  • 程序计数器决定着程序的流程。

1.4 条件分支和循环机制

  • 程序的流程分为顺序执行、条件分支和循环三种。顺序执行是指按照地址内容的顺序执行指令。条件分支是指根据条件执行任意地址的指令。循环是指重复执行同一地址的指令。
  • 程序中的比较指令,就是在CPU内部做减法运算。

1.5 函数的调用机制

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

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

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

1.7 CPU的处理其实很简单

  • 机器语言指令的主要类型和功能
  • 1位代表二进制数的一个字节位

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

  • XOR运算只反转与1相对应的位。NOT运算是反转所有的位
  • 也就是说,只要掌握了使用二进制数来表示信息的方法及其运算机制,也就自然能够了解程序的运行机制了。

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

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

2.2 什么是二进制数

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

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

  • 和十进制数一样,四则运算同样也可以使用在二进制数中,只要注意逢2进位即可
  • 移位运算指的是将二进制数值的各数位进行左右移位(shift=移位)的运算。
  • 移位操作使最高位或最低位溢出的数字,直接丢弃就可以了。
  • 十进制数左移后会变成原来的10倍、100倍、1000倍……同样,二进制数左移后就会变成原来的2倍、4倍、8倍……反之,二进制数右移后则会变成原来的1/2、1/4、1/8……

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

  • 要想区分什么时候补0什么时候补1,只要掌握了用二进制数表示负数的方法即可
  • 为此,在表示负数时就需要使用“二进制的补数”。补数就是用正数来表示负数

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

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

2.6 掌握逻辑运算的窍门

  • 计算机能处理的运算,大体可分为算术运算和逻辑运算。算术运算是指加减乘除四则运算。逻辑运算是指对二进制数各数字位的0和1分别进行处理的运算,包括逻辑非(NOT运算)、逻辑与(AND运算)、逻辑或(OR运算)和逻辑异或(XOR运算[插图])四种。
  • 逻辑运算的运算对象不是数值,因此不会出现进位的情况。看起来好像有些麻烦,总之就是不要将它作为数值来考虑。

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

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

3.2 用二进制数表示小数

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

3.3 计算机运算出错的原因

  • 计算机之所以会出现运算错误,是因为“有一些十进制数的小数无法转换成二进制数”。
  • 实际上,十进制数0.1转换成二进制后,会变成0.00011001100…(1100循环)这样的循环小数。这和无法用十进制数来表示1/3是一样的道理。1/3就是0.3333…,同样是循环小数。

3.4 什么是浮点数

  • 双精度浮点数类型用64位、单精度浮点数类型用32位来表示全体小数
  • 浮点数是指用符号、尾数、基数和指数这四部分来表示的小数

3.5 正则表达式和EXCESS系统

  • 十进制数的浮点数应该遵循“小数点前面是0,小数点后面第1位不能是0”这样的规则
  • EXCESS系统表现是指,通过将指数部分表示范围的中间值设为0,使得负数不需要用符号来表示。

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

  • 另一个策略是把小数转换成整数来计算。计算机在进行小数计算时可能会出错,但进行整数计算(只要不超过可处理的数值范围)时一定不会出现问题。
  • 在涉及财务计算等不允许出现误差的情况下,一定要将小数转换成整数或者采用BCD方法,以确保最终得到准确的数值。

3.8 二进制数和十六进制数

  • 在C语言程序中,只需在数值的开头加上0x(0和x)就可以表示十六进制数。
  • 通过使用十六进制数,二进制数的位数能够缩短至原来的1/4。

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

  • 物理内存是以字节为单位进行数据存储的。
  • 计算机是进行数据处理的设备,而程序表示的就是处理顺序和数据结构
  • 只要在程序上花一些心思,就可以将内存变换成各种各样的数据结构来使用

4.1 内存的物理机制很简单

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

4.2 内存的逻辑模型是楼房

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

4.3 简单的指针

  • 指针也是一种变量,它所表示的不是数据的值,而是存储着数据的内存的地址。
  • 假设d、e、f的值都是100。在这种情况下,使用d时就能够从编号100的地址中读写1个字节的数据,使用e时就是2个字节(100地址和101地址)的数据,使用f时就是4个字节(100地址~103地址)的数据。
  • 指针的数据类型表示一次可以读写的长度

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

  • 数组是指多个同样数据类型的数据在内存中连续排列的形式。作为数组元素的各个数据会通过连续的编号被区分开来,这个编号称为索引(index)。
  • 数组的定义中所指定的数据类型,也表示一次能够读写的内存大小。char类型的数组以1个字节为单位对内存进行读写,而short类型和long类型的数组则分别以2个字节、4个字节为单位对内存进行读写。
  • 之所以说数组是内存的使用方法的基础,是因为数组和内存的物理构造是一样的。特别是1字节类型的数组,它和内存的物理构造完全一致。

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

  • 栈用的是LIFO(Last Input First Out,后入先出)方式,而队列用的则是FIFO(First Input First Out,先入先出)方式。
  • 这种机制体现在内存上,就是栈。当我们需要暂时舍弃当前的数据,随后再原貌还原时,会使用栈。
  • 队列一般是以环状缓冲区(ring buffer)的方式来实现的

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

  • 在数组的各个元素中,除了数据的值之外,通过为其附带上下一个元素的索引,即可实现链表。数据的值和下一个元素的索引组合在一起,就构成了数组的一个元素。
  • 如果不使用链表数组,那么中途删除或追加元素时,其后的元素就必须要全部移动。

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

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

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

  • 磁盘缓存是指,把从磁盘中读出的数据存储在内存中,当该数据再次被读取时,不是从磁盘而是直接从内存中高速读出。
  • 利用电流来实现存储的内存,同利用磁效应来实现存储的磁盘,还是有差异的。而从存储容量来看,内存是高速高价,而磁盘则是低速廉价。

5.1 不读入内存就无法运行

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

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

  • 磁盘缓存指的是把从磁盘中读出的数据存储到内存空间中的方式。
  • 把低速设备的数据保存在高速设备中,需要时可以直接将其从高速设备中读出,这种缓存的方式在其他情况下也会用到。

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

  • 虚拟内存是指把磁盘的一部分作为假想的内存来使用。这与磁盘缓存是假想的磁盘(实际上是内存)相对,虚拟内存是假想的内存(实际上是磁盘)。
  • 虚拟内存的方法有分页式和分段式[插图]两种。

5.4 节约内存的编程方法

  • 以图形用户界面(GUI, Graphical User Interface)为基础的Windows,可以说是一个巨大的操作系统。
  • 由于使用虚拟内存时发生的Page In和Page Out往往伴随着低速的磁盘访问,因此在这个过程中应用的运行会变得迟钝起来
  • 在C语言中,函数的返回值,是通过寄存器而非栈来返回的。

5.5 磁盘的物理结构

  • 磁盘是通过把其物理表面划分成多个空间来使用的。划分的方式有扇区方式和可变长方式两种,前者是指将磁盘划分为固定长度的空间,后者则是指把磁盘划分为长度可变的空间。
  • 不管是硬盘还是软盘,不同的文件是不能存储在同一个簇中的,否则就会导致只有一方的文件不能被删除。因此,不管是多么小的文件,都会占用1簇的空间。这样一来,所有的文件都会占用1簇的整数倍的磁盘空间。
  • 磁盘的数据保存是以簇为单位来进行

第6章 亲自尝试压缩数据

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

6.1 文件以字节为单位保存

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

6.2 RLE算法的机制

  • 由于半角字母中,1个字符是作为1个字节的数据被保存在文件中的。
  • 这时,大家是不是会采取将文件的内容用“字符×重复次数”这样的表现方式来压缩呢。
  • 把文件内容用“数据×重复次数”的形式来表示的压缩方法称为RLE(Run Length Encoding,行程长度编码)算法(图6-2)。

6.3 RLE算法的缺点

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

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

  • 哈夫曼算法的关键就在于“多次出现的数据用小于8位的字节数来表示,不常用的数据则可以用超过8位的字节数来表示”
  • 莫尔斯编码不是通过语言,而是通过“嗒嘀嗒嘀”这些长点和短点的组合来传递文本信息的。想

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

  • 哈夫曼算法是指,为各压缩对象文件分别构造最佳的编码体系,并以该编码体系为基础来进行压缩。
  • 而在哈夫曼算法中,通过借助哈夫曼树构造编码体系,即使在不使用字符区分符号的情况下,也可以构建能够明确进行区分的编码体系。也就是说,利用哈夫曼树后,就算表示各字符的数据位数不同,也能够做成可以明确区分的编码。

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

  • 使用哈夫曼树后,出现频率越高的数据所占用的数据位数就越少,而且数据的区分也可以很清晰地实现。
  • 而从用哈夫曼算法压缩过的文件中读取数据后,就会以位为单位对该数据进行排查,并与哈夫曼树进行比较看是否到达了目标编码

6.7 可逆压缩和非可逆压缩

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

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

  • 游戏机是一边把CD中存储的软件部分复制入内存,一边运行游戏的。
  • 盒式卡带的情况下,可以将游戏机主机的内存完整置换,所以不需要往内存中复制数据。只有磁盘才必须把数据复制到内存中。

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

  • 不同的硬件种类需要不同的操作系统。
  • 运行环境不同指的是什么呢?为什么运行环境不同,应用就无法运行呢?

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

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

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

  • 计算机的硬件并不仅仅是由CPU构成的,还包括用于存储程序指令和数据的内存,以及通过I/O连接的键盘、显示器、硬盘、打印机等外围设备。
  • Windows操作系统对克服这些硬件构成的差异做出了很大贡献。
  • MS-DOS应用大多都是不经过操作系统而直接控制硬件的,而Windows应用则基本上都由Windows来完成对硬件的控制

7.3 不同操作系统的API不同

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

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

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

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

  • 即使不通过移植,也可以使用别的方法来运行其他操作系统的应用。这里我们要介绍的方法就是利用虚拟机软件。
  • Virtual PC for MAC可以使Macintosh这一硬件变得同AT兼容机一样,从而能在该硬件上安装Windows。

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

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

7.7 BIOS和引导

  • 引导程序是存储在启动驱动器起始区域的小程序。操作系统的启动

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

  • 通过对源文件进行编译,得到目标文件。例如,C语言中,将Sample1.c这个源文件编译后,就会得到Sample1.obj这个目标文件。目标文件的内容是本地代码。
  • 源代码完成后,就可以编译生成可执行文件了。负责实现该功能的是编译器

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

  • 计算机只能运行本地代码
  • 用某种编程语言编写的程序就称为源代码[插图],保存源代码的文件称为源文件。

8.2 本地代码的内容

  • Windows中EXE文件的程序内容,使用的就是本地代码。
  • 也正是因为如此,才有了用人类容易理解的C语言等编程语言来编写源代码,然后再将源代码转换成本地代码这一方法。
  • 。Dump是指把文件的内容,每个字节用2位十六进制数来表示的方式。

8.3 编译器负责转换源代码

  • 读入的源代码还要经过语法解析、句法解析、语义解析等,才能生成本地代码。
  • 一种交叉编译器,它生成的是和运行环境中的CPU不同的CPU所使用的本地代码

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

  • 为了得到可以运行的EXE文件,编译之后还需要进行“链接”处理。
  • 编译后生成的不是EXE文件,而是扩展名为“.obj”的目标文件[插图]
  • 把多个目标文件结合,生成1个EXE文件的处理就是链接,运行连接的程序就称为链接器(linkage editor或连结器)

8.5 启动及库文件

  • c0w32.obj这个目标文件记述的是同所有程序起始位置相结合的处理内容,称为程序的启动。
  • 库文件指的是把多个目标文件集成保存到一个文件中的形式。链接器指定库文件后,就会从中把需要的目标文件抽取出来,并同其他目标文件结合生成EXE文件。
  • sprintf()等函数,不是通过源代码形式而是通过库文件形式和编译器一起提供的。这样的函数称为标准函数。之所以使用库文件,是为了简化为链接器的参数指定多个目标文件这一过程。

8.6 DLL文件及导入库

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

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

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

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

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

8.9 有点难度的Q&A

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

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

  • What You See Is What Your Get(所见即所得)
  • 监控程序也可以说是操作系统的原型。
  • 程序员的工作就是编写各种各样的应用来提高业务效率。

9.1 操作系统功能的历史

  • 有人开发出了仅具有加载和运行功能的监控程序,这就是操作系统的原型。
  • 操作系统本身并不是单独的程序,而是多个程序的集合体

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

  • 要想成为一个全面的程序员,有一点需要清楚的是,掌握基本的硬件知识,并借助操作系统进行抽象化,可以大大提高编程效率。
  • 在操作系统这个运行环境下,应用并不是直接控制硬件,而是通过操作系统来间接控制硬件的。变量定义中涉及的内存的申请分配,以及time()和printf()这些函数的运行结果,都不是面向硬件而是面向操作系统的。操作系统收到应用发出的指令后,首先会对该指令进行解释,然后会对时钟IC(实时时钟[插图])和显示器用的I/O进行控制。

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

  • 操作系统的硬件控制功能,通常是通过一些小的函数集合体的形式来提供的。这些函数及调用函数的行为统称为系统调用(system call),也就是应用对操作系统(system)的功能进行调用(call)的意思。
  • 高级编程语言的机制就是,使用独自的函数名,然后再在编译时将其转换成相应操作系统的系统调用(也有可能是多个系统调用的组合)

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

  • 这是因为操作系统和高级编程语言能够使硬件抽象化。这是个非常了不起的处理。
  • 文件是操作系统对磁盘媒介空间的抽象化。
  • 磁盘媒介的读写采用了文件这个概念,将整个流程抽象化成了打开文件用的fopen()、写入文件用的fputs()、关闭文件用的fclose()

9.5 Windows操作系统的特征

  • Windows操作系统的主要特征如下所示。(1)32位操作系统(也有64位版本)(2)通过API函数集来提供系统调用(3)提供采用了图形用户界面的用户界面(4)通过WYSIWYG[插图]实现打印输出(5)提供多任务功能(6)提供网络功能及数据库功能(7)通过即插即用实现设备驱动的自动设定
  • 这里的32位表示的是处理效率最高的数据大小
  • Windows是通过名为API的函数集来提供系统调用的。API是联系应用程序和操作系统之间的接口。所以称为API(Application Programming Interface,应用程序接口)。
  • API通过多个DLL文件来提供。各API的实体都是用C语言编写的函数。
  • 在像MS-DOS这种没有使用GUI的操作系统中,应用的处理流程由程序员决定,用户按照定好的流程来进行操作即可。与此相反,采用GUI的操作系统中运行的应用,则是由用户决定处理流程的。因此,程序员就必须要制作出在任何操作顺序下都能运行的应用
  • 多任务指的是同时运行多个程序的功能。Windows是通过时钟分割技术来实现多任务功能的。
  • 程序是操作系统、中间件、应用等所有软件的统称

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

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

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

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

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

  • 大部分C语言编译器,都可以把利用C语言编写的源代码转换成汇编语言的源代码,而不是本地代码
  • 汇编语言源文件的扩展名,通常用“.asm”来表示

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

  • 汇编语言的源代码,是由转换成本地代码的指令(后面讲述的操作码)和针对汇编器的伪指令构成的。
  • 由伪指令segment和ends围起来的部分,是给构成程序的命令和数据的集合体加上一个名字而得到的,称为段定义
  • 即使在源代码中指令和数据是混杂编写的,经过编译或者汇编后,也会转换成段定义划分整齐的本地代码
  • 伪指令proc和endp围起来的部分,表示的是过程(procedure)的范围。在汇编语言中,这种相当于C语言的函数的形式称为过程。

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

  • 汇编语言指令的语法结构是操作码+操作数[插图](也存在只有操作码没有操作数的指令)。
  • 操作数中指定了寄存器名、内存地址、常数等
  • 寄存器是CPU中的存储区域。不过,寄存器并不仅仅具有存储指令和数据的功能,也有运算功能

10.5 最常用的mov指令

  • 如果指定了没有用方括号围起来的内容,就表示对该值进行处理;如果指定了用方括号围起来的内容,方括号中的值则会被解释为内存地址,然后就会对该内存地址对应的值进行读写操作。
  • dwordptr(double word pointer)表示的是从指定内存地址读出4字节的数据

10.6 对栈进行push和pop

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

10.7 函数调用机制

  • 函数调用是栈发挥大作用的场合。
  • 最优化功能是编译器在本地代码上费尽功夫实现的,其目的是让编译后的程序运行速度更快、文件更小。

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

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

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

  • 局部变量是临时保存在寄存器和栈中的
  • 例如,在函数入口处为变量申请分配栈的内存空间的话,就必须在函数出口处进行释放。否则,经过多次调用函数后,栈的内存空间就会被用光了。

10.11 循环处理的实现方法

  • 在汇编语言的源代码中,循环是通过比较指令(cmp)和跳转指令(jl)来实现的。
  • 汇编语言中比较指令的结果,会存储在CPU的标志寄存器中
  • 汇编语言是对CPU的实际运行进行直接描述的低级编程语言,C语言是用与人类的感觉相近的表现来描述的高级编程语言

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

  • 如果只是看counter *=2;的话,就会以为counter的数值被直接扩大为了原来的2倍。然而,实际上执行的却是“把counter的数值读入eax寄存器”“将eax寄存器的数值变成原来的2倍”“把eax寄存器的数值写入counter”这3个处理。
  • 在多线程处理中,用汇编语言记述的代码每运行1行,处理都有可能切换到其他线程(函数)中。
  • 为了避免该bug,我们可以采用以函数或C语言源代码的行为单位来禁止线程切换的锁定方法。通过锁定,在特定范围内的处理完成之前,处理不会被切换到其他函数中。至于为什么要锁
  • 如果大家会使用C语言的话,希望大家对C语言的各种语法所对应的汇编语言都一一确认一下。最好能编写一些简短的程序来进行反复的测试。笔者自身也是通过进行这些尝试才使自己的编程技能有了大幅提高的。

第11章 硬件控制方法

  • DMA指的是,不经过CPU中介处理,外围设备直接同计算机的主内存进行数据传输。
  • 控制CPU,只需把编译器或汇编器生成的本地代码加载到主内存并运行就可以了

11.1 应用和硬件无关?

  • 利用操作系统提供的系统调用功能就可以实现对硬件的控制。在Windows中,系统调用称为API(图11-1)。各API就是应用调用的函数。这些函数的实体被存储在DLL文件中。
  • Windows做了什么呢?从结果来看,Windows直接控制了作为硬件的显示器。但Windows本身也是软件,由此可见,Windows应该向CPU传递了某些指令,从而通过软件控制了硬件。

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

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

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

  • 虽然蜂鸣器内置在计算机内部,但其本身也是外围设备的一种。因为就算是把蜂鸣器取出,对计算机主机也不会有什么影响。
  • 在大部分C语言的处理(编译器的种类)中,只要使用_asm{和}括起来,就可以在其中记述助记符。也就是说,这样就可以编写C语言和汇编语言混合的源代码

11.4 外围设备的中断请求

  • IRQ是用来暂停当前正在运行的程序,并跳转到其他程序运行的必要机制。该机制称为中断处理
  • 中断处理程序运行结束后,处理也会返回到主程序中继续

11.5 用中断来实现实时处理

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

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

  • DMA是指在不通过CPU的情况下,外围设备直接和主内存进行数据传送。
  • I/O端口号、IRQ、DMA通道可以说是识别外围设备的3点组合

11.7 文字及图片的显示机制

  • 显示器中显示的信息一直存储在某内存中。该内存称为VRAM(Video RAM)。在程序中,只要往VRAM中写入数据,该数据就会在显示器中显示出来。实现该功能的程序,是由操作系统或BIOS提供,并借助中断来进行处理的。
  • 虽然计算机领域的新技术在不断涌现,但计算机能处理的事情,始终只是对输入的数据进行运算,并把结果输出,这一点是不会发生任何变化的

第12章 让计算机“思考”

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

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

  • 控制就是指CPU和各种设备之间配合进行数据的输入输出处理
  • 另外一个使用目的是用程序来代替执行人类的思考过程。

12.4 程序生成随机数的方法

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

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

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

C语言的特点

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

变量和函数

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

数据类型

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

标准函数库

  • 函数包括程序员自己编写的函数以及系统提供的函数。其中,后者通常称为标准函数库。标准函数库是指具有可被各种程序使用的通用功能的函数。
  • 函数的括号中,除变量以外,也可以放置通过文字串、数值等指定的数据信息,这些统称为参数。被作为函数的处理结果而返回的数值称为返回值。利用函数称为函数调用。
  • 计算机的基本操作大体可以划分为“输入数据”“处理数据”“输出数据”三块。

函数调用

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

局部变量和全局变量

  • 在函数模块中定义的变量,只能在该函数中使用。这样的变量就称为局部变量。
  • 变量也可以在函数模块外进行定义(虽然函数处理必须要在函数的模块中进行,但变量是可以在模块外进行定义的),该变量称为全局变量。

其他语法结构

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

结语

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