第一章 计算机系统漫游

第一章 计算机系统漫游

    计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。虽然系统的具体实现随着时间不断变化,但是系统内在的概念并没有改变。所有计算机都由相似的硬件和软件组成,它们又执行着相似的功能。一些程序员希望了解这些组件是如何工作的,以及,这些组件是如何影响程序的正确性和性能的,以此来提高自身的技能。本书便是为这些读者而写的。
    现在就要开始一段有趣的漫游经历了。如果你全力投入 学习本书中的概念,完全理解底层计算机系统以及它对应用程序的影响,那么你会成为步上为数不多的“大牛”的道路。
    你将会学习一些实践技巧,比如,如何避免由计算机表示数字的方式,引起的奇怪的数字错误。你将会学习怎样通过一些小窍门来优化你编写的 C 代码,以充分利用现代处理器和存储器系统的设计。你将了解编译器是如何实现过程调用的,以及如何利用这些知识来避免缓冲区溢出错误,带来的安全漏洞,这些弱点给网络和英特网软件带来了巨大的麻烦。你讲学会如何识别和避免链接时那些令人讨厌的错误,它们困扰着普通的程序员。你将学会如何编写自己的Unix shell、自己的动态存储分配包,甚至自己的Web服务器。你会认识并发带来的希望和陷阱,这个主题随着单个芯片上集成了多个处理器核,变得越来越重要。
    在 Kernighan 和 Ritchie 的关于 C 语言编程的经典教材【61】中,他们通过图1-1所示的 hello 程序来向读者介绍 C。尽管 hello 程序非常简单,但是为了让它实现运行,系统的每个主要 组成部分 都需要协调工作。从某种意义上来说,本书的目的,就是要帮助你了解当你在系统上执行 hello 程序时,系统发生了什么,以及 为什么会这样。
在这里插入图片描述
    我们通过跟踪 hello 程序的生命周期,来开始对系统的学习。从它被程序员创建开始,到在系统上运行,输出简单的消息,然后终止。我们将 沿着这个程序的生命周期,简要的介绍一些逐步出现的关键概念、专业术语和组成部分。后面的章节将围绕这些内容展开。

1.1 信息 就是 位 + 上下文

    hello 程序的生命周期是从一个 源程序 (或者说是 源文件 )开始的,即程序员通过编辑器创建并保存的文本文件,文件名是 hello.c。源程序实际上就是一个由值 0 和 1 组成的位(又称为 比特),8个位被组织成一组,称为 字节。每个字节表示程序中的某些文本字符。
    大部分的现代计算机系统都是用 ASCII 标准来表示文本字符,这种方式实际上就是用一个唯一的单字节大小的整数值来表示每个字符。比如,图 1-2 中给出了 hello.c 程序的 ASCII 表示。
在这里插入图片描述
    hello.c 程序是以 字节序列的方式,储存在文件中的。每个字节都有一个整数值, 对应与某些字符。例如,第一个字节的整数值是 35,它对应的字符就是 “#”。第二个字节的整数值为 105,它对应的字符是 “i”,以此类推。注意,每个文本行,都是以 一个看不见的换行符 \n 来结束的。像 hello.c 这样只由 ASCII 字符构成的文件称为 文本文件,所有其他文件称为 二进制文件。
    hello.c 的表示方法,说明了一个思想:系统中的所有信息,包括 磁盘文件、内存中的程序、内存中存放的用户数据 以及 网络上传送的数据,都是由 一串比特表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文。比如,在不同的上下文中,一个同样的字节序列可能表示 一个整数、浮点数、字符串 或者 机器指令。
    作为程序员,我们需要了解数字的机器表示方式,因为它们与实际的整数和实数是不同的。它们是对真值的有限近似值,有时会有意想不到的行为表现。这方面的基本原理将在第 2 章中详细描述。
在这里插入图片描述
在这里插入图片描述

1.2 程序被其他程序翻译成不同的格式

    hello 程序的生命周期是从一个高级 C 语言程序开始的,因为这种形式能够被人读懂。然而 为了能在系统上运行 hello.c 程序,每条 C 语言都必须被其他程序转化为一系列的低级机器语言指令 。然后这些指令按照一种称为 可执行目标程序 的格式打好包,并以二进制磁盘文件的形式,存放起来。目标程序也称为 可执行目标文件。
    在 Unix 系统上,从 源文件 变成 目标文件 的转化,是由 编译器驱动程序 完成的:
    linux> gcc -o hello hello.c
    在这里,GCC编译器驱动程序读取 源程序文件 hello.c,并把它翻译成一个可执行的目标文件 hello。这个翻译过程可分为四个阶段完成,如图 1-3 所示。执行这四个阶段(预处理器、编译器、汇编器、链接器)的程序,一起构成了编译系统(compilation system)。
在这里插入图片描述

  1. 预处理阶段。 预处理器(cpp)根据字符 # 开头的命令,修改原始的 C 程序,比如 hello.c 中的第 1 行的 #include<stdio.h>命令,告诉 预处理器 读取系统头文件 stdio.h 的内容,并把它直接插入程序文本中。结果就得到了另一个 C 程序,通常是以 .i 作为文件扩展名。
  2. 编译阶段。 编译器(ccl)将文本文件 hello.i 翻译成 文本文件 hello.s,它包含一个汇编语言程序。该程序包含 函数main的定义,如下所示:
    在这里插入图片描述
    定义中 2~7 行的每条语句都以一种文本格式描述了一条 低级机器语言指令。汇编语言是非常有用的,因为它为不同高级语言的不同编译器提供了通用的输出语言。例如,C 编译器 和 Fortran 编译器 产生的输出文件,用的都是一样的汇编语言。
  3. 汇编阶段。 接下来,汇编器(as)将 hello.s 翻译成 机器语言指令,把这些指令打包成一种叫做 可重定位目标程序(relocatable object program)的格式,并把结果保存在目标文件的 hello.o 中。hello.o 文件是一个二进制文件,它包含的 17 个字节是函数main的指令编码。如果我们在文本编辑器中打开 hello.o 文件,将看到一堆乱码。
  4. 链接阶段。 请注意,hello 程序调用了 printf 函数,它是每个 C 编译器都提供的标准 C 库中的一个函数。printf 函数存在于 一个名为 printf.o 的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的 hello.o 程序中。链接器(ld)就负责处理这种合并。结果就得到 hello 文件,它是一个可执行目标文件(或者简称为 可执行目标文件),可以被加载到内存中,由系统执行。
    在这里插入图片描述

1.3 了解编译系统如何工作是大有益处的

    对于像 hello.c 这样简单的程序,我们可以依靠 编译系统 生成有效的机器代码。但是,有一些重要的原因促使程序员必须知道编译系统是如何工作的
5. 优化程序性能。 现代编译器都是成熟的工具,通常可以生成很好的代码。作为程序员,我们无需为了写出高效代码二区了解编译器的内部工作。但是,为了在 C 程序中做出好的编码选择,我们需要了解一些机器代码 以及 编译器将不同的 C 语句转化为机器代码的方式。比如,一个 switch 语句是否总是比一系列的 if-else 高效的多?一个函数调用的开销有多大?while 循环比 for 循环更有效吗?指针引用比数组引用更有效吗?为什么将循环求和的结果放到一个本地变量中,会比 将其放到一个 通过引用传递过来的参数 中,运行起来快很多呢?为什么我们只是简单的重新排列一下算数表达式中的括号就能让函数运行得更快呢?
    在第 3 章中,我们将介绍 x86-64,最近几代 Linux、Macintosh 和 Windows 计算机的计算语言。我们将讲述编译器是如何把不同的 C 语言结构翻译成这种 机器语言的。在第 5 章中,你将学习如何通过简单转换 C 语言,帮助编译器更好的完成工作,从而调整 C 程序的性能。在第 6 章中,你将学习存储器系统的层次结构特征, C 语言编译器如何将数组存放在内存中,以及 C 程序又是如何利用这些知识从而更高效的运行。
6. 理解链接时出现的错误。 根据我们的经验,一些最令人困扰的程序错误,往往都与链接器操作有关。尤其是,当你试图构建大型的软件系统时。比如,链接器报告说,它无法解析 一个引用,这是什么意思?静态变量和全局变量的区别是什么?如果你在两个不同的 C 文件中定义了相同的两个全局变量,会发生什么?静态库和动态库的区别是什么?我们在命令行上排列库的顺序有什么影响?最严重的是,为什么有些错误,直到链接时,才会出现?在第 7 章中,你将会得到这些答案
7. 避免安全漏洞。 多年来,<

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值