《程序是怎样跑起来的》读书笔记

Posted Mount256

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《程序是怎样跑起来的》读书笔记相关的知识,希望对你有一定的参考价值。

一本比较适合对计算机零认识的人看的科普书,因为很简单,我们快速读完,并完成了这篇测试笔记,该笔记提取了我们认为值得了解的要点。

文章目录

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

5.4 节约内存的编程方法

(1)通过 DLL 文件实现函数共用

  • DLL(Dynamic Link Library)文件:在程序运行时可以动态加载 Library(函数和数据的集合)的文件。
  • 静态链接导致内存利用效率下降:假设我们编写了一个具有某些处理功能的函数 MyFunc()。应用 A 和应用 B 都会使用这个函数。在各个应用的运行文件中内置函数 MyFunc()(这个称为 Static Link,静态链接)后同时运行这两个应用,内存中就存在了具有同一函数的两个程序。
  • 解决办法:函数 MyFunc() 是独立的 DLL 文件而不是应用的执行文件。

(2)通过调用 _stdcall 来减小程序文件的大小

__stdcall 是函数调用约定的一种,函数调用约定主要约束了两件事:

  • 参数传递顺序
  • 调用堆栈由谁(调用函数或被调用函数)清理
  • 适用范围:C 语言
  • _stdcall 是 standard call(标准调用)的简称。Windwos 提供的 DLL 文件内的函数,基本上都是 _stdcall 调用方式。这主要是为了节约内存。另一方面,用 C 语言编写的程序内的函数,默认设置都不是 _stdcall。C 语言特有的调用方式称为 C 调用。C 语言之所以默认不使用 _stdcall,是因为 C 语言所对应的函数的传入参数是可变的(可以设定任意参数),只有函数调用方才能知道到底有多少个参数,而这种情况下,栈的清理作业便无法进行。不过,在 C 语言中,如果函数的参数数量固定的话,指定 _stdcall 是没有任何问题的。
  • 栈清理处理:把不需要的数据从接收和传递函数的参数时使用的内存上的栈区域中清理出去。编译器将程序翻译为汇编程序,默认将栈清理处理添加在函数调用方。
  • 在同一个程序中,同样的函数可能会被多次反复调用。而如果是同样的函数,栈清理处理的内容也是一样的。由于该处理是在调用函数一方,因此就会导致同一处理被反复进行(比如,主函数三次调用子函数,则连接后整个代码会出现三次栈清理)。这就造成了内存的浪费。在函数前加上 _stdcall,就可以把栈清理处理变为在被调用函数一方进行(比如,子函数内部自己清理栈,则主函数三次调用子函数,连接后整个代码只会出现一次栈清理)。

第 6 章 亲自尝试压缩数据

6.2 RLE 算法的机制

  • 把文件内容用“数据×重复次数”的形式来表示的压缩方法称为 RLE(Run Length Encoding,行程长度编码)算法。比如:AAAAAABBCDDEEEEEF 就可以用 A6B2C1D2E5F1 表示。

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

  • 哈夫曼算法的关键就在于“多次出现的数据用小于 8 位的字节数来表示,不常用的数据则可以用超过 8 位的字节数来表示”。假如:A 出现了 100 次左右,Q 仅用到了 3 次,则 A 和 Q 都用 8 位来表示时,原文件的大小就是 100 次 × 8 位 + 3 次 × 8 位 = 824 位,而假设 A 用 2 位、Q 用 10 位来表示,压缩后的大小就是 100 次 × 2 位+3 次 × 10 位 = 230 位。

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

  • 利用哈夫曼树后,就算表示各字符的数据位数不同,也能够做成可以明确区分的编码。

6.6 可逆压缩和非可逆压缩

  • 我们把能还原到压缩前状态的压缩称为可逆压缩,无法还原到压缩前状态的压缩(即一部分数据会被丢失)称为非可逆压缩
  • Windows 的标准图像数据形式为 BMP ,是完全未压缩的。由于显示器及打印机输出的 bit(点)是可以直接映射(mapping)的,因此便有了 BMP = bitmap 这一名称。
  • JPEG 格式的文件是非可逆压缩。GIF 格式的文件是可逆压缩。

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

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

  • 机器语言的程序称为本地代码(native code)程序员用 C 语言等编写的程序,在编写阶段仅仅是文本文件。文本文件在任何环境下都能显示和编辑。我们称之为源代码

7.3 不同操作系统的 API 不同

  • 应用程序向操作系统传递指令的途径称为 API(Application Programming Interface)。Windows 及 Unix 系列操作系统的 API,提供了任何应用程序都可以利用的函数组合。因为不同操作系统的 API 是有差异的,因此,将同样的应用程序移植到其他操作系统时,就必须要重写应用中利用到 API 的部分。像键盘输入 、鼠标输入、显示器输出、文件输入输出等同外围设备进行输入输出操作的功能,都是通过 API 提供的。

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

  • Unix 系列操作系统 FreeBSD 中,存在一种名为 Ports 的机制。该机制能够结合当前运行的硬件环境来编译应用的源代码,进而得到可以运行的本地代码系统。如果目标应用的源代码没有在硬件上的话,Ports 就会自动使用 FTP 连接到相关站点来下载代码。

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

  • 除虚拟机的方法之外,还有一种方法能够提供不依赖于特定硬件及操作系统的程序运行环境,那就是 Java。
  • Java 有两个意思:一个是作为编程语言的 Java,另一个是作为程序运行环境的 Java。同其他编程语言相同,Java 也是将 Java 语法记述的源代码编译后运行。不过,编译后生成的并不是特定 CPU 使用的本地代码,而是名为字节代码的程序。字节代码的运行环境就称为Java 虚拟机(JavaVM,Java Virtual Machine)。Java 虚拟机是一边把 Java 字节代码逐一转换成本地代码一边运行的。
  • 编译器会将程序员编写的源代码(sample.java)转换成字节代码(sample.class)。而 Java 虚拟机(java.exe)则会把字节代码变换成 x86 系列 CPU 适用的本地代码,然后由 x86 系列 CPU 负责实际的处理。

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

8.2 本地代码的内容

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

8.3 编译器负责转换源代码

  • 能够把 C 语言等高级编程语言编写的源代码转换成本地代码的程序称为编译器。每个编写源代码的编程语言都需要其专用的编译器。将 C 语言编写的源代码转换成本地代码的编译器称为 C 编译器。编译器本身也是程序的一种,所以也需要运行环境。例如,有 Windows 用的 C 编译器、Linux 用的 C 编译器等。

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

  • Borland C++ 的编译器是 bcc32.exe 这个命令行工具。在 Windows 的命令提示符中,运行下列命令后, 由 C 语言编写的源文件 Sample1.c 就会被编译。

bcc32 -W -c Sample1.c

-W-c是用来指定编译 Windows 用的程序的选项。选项是对编译器的指示。有时也称为“开关”。

  • 编译后生成的不是 EXE 文件,而是扩展名为“.obj”的目标文件。Sample1.c 编译后,就生成了 Sample1.obj 目标文件。

8.5 启动及库文件

  • 把多个目标文件结合,生成 1 个 EXE 文件的处理就是链接,运行连接的程序就称为链接器(linkage editor 或连结器)。Borland C++ 的链接器就是 ilink32.exe 的命令行工具。在 Windows 命令提示符下运行以下命令后,程序所需的目标文件就会被全部链接生成 Sample1.exe 这个 EXE 文件。

ilink32 -Tpe -c -x -aa c0w32.obj Sample1.obj, Sample1.exe, import32.lib cw32.lib

链接选项“-Tpe-c-x-aa”是指定生成 Windows 用的 EXE 文件的选项。在这些选项之后,会指定结合的目标文件。而该命令行中就指定了 c0w32.obj、Sample1.obj 这两个目标文件。Sample1.obj 是 Sample1.c 编译后得到的目标文件。c0w32.obj 这个目标文件记述的是同所有程序起始位置相结合的处理内容,称为程序的启动。c0w32.obj 是由 Borland C++ 提供的。

  • 比如,sprintf() 的目标文件在 cw32.lib 中,MessageBox() 的目标文件在 import32.lib 中。像 import32.lib 及 cw32.lib 这样的文件称为库文件。库文件指的是把多个目标文件集成保存到一个文件中的形式。链接器指定库文件后,就会从中把需要的目标文件抽取出来,并同其他目标文件结合生成 EXE 文件。

  • 外部符号是指其他目标文件中的变量或函数。sprintf() 等函数,不是通过源代码形式而是通过库文件形式和编译器一起提供的。这样的函数称为标准函数。之所以使用库文件,是为了简化为链接器的参数指定多个目标文件这一过程。例如,在链接调用了数百个标准函数的程序时,就要在链接器的命令行中指定数百个目标文件,这样就太繁琐了。而利用存储着多个目标文件的库文件的话,则只需在链接器的命令行中指定几个库文件就可以了。

8.6 DLL 文件及导入库

  • Windows 中,API 的目标文件,并不是存储在通常的库文件中,而是存储在名为 DLL(Dynamic Link Library)文件的特殊库文件中。
  • 实际上,import32.lib 中仅仅存储着两个信息,一是 MessageBox() 在 user32.dll 这个 DLL 文件中,另一个是存储着 DLL 文件的文件夹信息,MessageBox() 的目标文件的实体实际上并不存在。我们把类似于 import32.lib 这样的库文件称为导入库
  • 与此相反,存储着目标文件的实体,并直接和 EXE 文件结合的库文件形式称为静态链接库。存储着 sprintf() 的目标文件的 cw32lib 就是静态链接库。
  • 通过结合导入库文件,执行时从 DLL 文件中调出的 MessageBox() 函数这一信息就会和 EXE 文件进行结合。这样,链接器链接时就不会再出现错误消息,从而就可以顺利编写 EXE 文件。

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

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

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

  • 当程序加载到内存后,除此之外还会额外生成两个空间,那就是栈和堆。栈是用来存储函数内部临时使用的变量(局部变量),以及函数调用时所用的参数的内存区域。堆是用来存储程序运行时的任意数据及对象的内存领域
  • 栈中对数据进行存储和舍弃(清理处理)的代码,是由编译器自动生成的,因此不需要程序员的参与。使用栈的数据的内存空间,每当函数被调用时都会得到申请分配,并在函数处理完毕后自动释放。与此相对,堆的内存空间,则要根据程序员编写的程序,来明确进行申请分配或释放。

----------------------------EOF----------------------------

以上是关于《程序是怎样跑起来的》读书笔记的主要内容,如果未能解决你的问题,请参考以下文章

《程序是怎样跑起来的》chap1~chap4笔记

《汇编语言第三版(王爽)》 读书笔记

读书笔记

《程序是怎样跑起来的》第八章读后感

读《程序是怎样跑起来的》第一章有感

《程序是怎样跑起来的》第二章