《深入理解程序设计》之计算机体系结构

在学习编程时,你首先要了解计算机如何解释程序。当然,你无需拥有电子工程学位,但需要理解一些基本概念。
现代计算机的体系结构都是在冯诺依曼体系结构(因其创始人而得名)的基础上发展起来的。冯诺依曼体系结构蒋计算机划分成两个主要组成部分:CPU(中央处理单元)和内存。这种架构被用在包括个人计算机,超级计算机,大型机在内所有现代计算机,甚至手机中。

2.1 内存结构

为理解计算机内存的结构,可以蒋计算机内存与你家附近的邮局做个比较。邮局通常有一个摆满邮政信箱的房间,计算机内存就像邮局房间里的信箱一样,每个固定大小的存储单元都依次编号。例如,大小为256M的内存大致包含2.56亿个固定大小的存储单元;如果与邮局类比,就是2.56亿个信箱。计算机内存的每个存储单元都有一个编号,所有存储单元都具有相同的固定大小。邮政信箱与计算机内存的不同之处在于:信箱中能存放各种东西,但是内存的存储单元中只能存一个数字。
你也许想知道为什么计算机如此设计。答案是:这样比较容易实现。设想一下,如果组成计算机内存的存储单元的大小各不相同,或者能在不同的存储单元存储不同类型的数据,要么实现起来不仅困难,而且成本太高。
计算机内存有多种用途。计算机的所有计算结果都存储在内存中。事实上,所有要“存储”的内容都会先存储在内存中。以你家里的计算机为例,想想当它运行时,在它的内存里存放了哪些东西。通常有(但不限于)这些:

  • 你的光标在屏幕上所在的位置;
  • 屏幕上每个窗口的大小;
  • 所使用的字体中每个字母的形状;
  • 每个窗口上所有控件的布局;
  • 工具栏上所有图标的图形;
  • 所有的错误信息和对话框的文本。
    除上述内容外 ,在采用冯诺依曼体系结构的计算机中,不仅计算机数据应存放在内存中。而且在控制计算机操作的程序也应存放在内存中。事实上,对计算机来说,程序和数据的区别仅仅在于计算机使用两者的方式不同,两者的存储和访问方式是一样的。

2.2 CPU构造

那么,计算机是怎么运行的呢?显然,仅仅能够存储数据是不行的,还必须能访问,操作以及移动数据。这时候,就用到CPU了。
CPU一次从内存中读取一条指令并执行,CPU执行指令的上述步骤就是所谓的读取-执行周期(又称为指令周期或机器周期)。为完成此功能,CPU需包含以下元件:

  • 程序计数器
  • 指令解码器
  • 数据总线
  • 通用寄存器
  • 算数逻辑单元
    程序计数器用来告诉计算机从哪里提取下一条指令。我们之间曾提到,数据和程序的存储方式并无区别,只有CPU对两者的解释方式不同。程序计数器保存即将执行的下一条指令的内存地址。CPU先查看程序计数器,然后提取存放在制定内存地址的数字,接着传递给指令解码器,由它来解析指令。指令解码器给出的解释包括:需要进行何种处理(加法,减法,乘法,移动数据等),以及处理过程中将会涉及哪些内存单元。计算机指令通常由实际指令和执行指令要用到的内存单元列表构成。
    接着,计算机使用数据总线取得存储在内存单元中的用于计算的数据。数据总线是CPU和内存间的物理连线,是两者的纽带。计算你主板上的那些从内存延伸出来的排线,就是数据总线。
    除了位于CPU外面的内存,CPU内部还包含一些被称为寄存器的特殊高速存储单元。CPU中有两种寄存器:通用寄存器和专用寄存器。通用寄存器是进行主要运算的地方,一般用来处理加法,减法,乘法,比较及其他运算。但是计算机CPU的通用寄存器很少,计算时用到的绝大多数信息都存储在主内存中,只有在CPU处理时才提取到通用寄存器,处理完成后再放回内存。专用寄存器是用于特定用途的寄存器,稍后我们再介绍。
    CPU在取回需要的数据后,蒋数据和经过解码的指令传递给算数逻辑单元进一步处理。算数逻辑单元是实际执行指令的地方。它得出计算结果后,蒋结果经数据总线传到指令指定的相应内存单元或寄存器。
    以上是对CPU处理指令的过程的一个相当简化的描述。最近几年,CPU已经有了很大的发展,变得越来越复杂。尽管基本操作任然相同,但新的cpu使用了多层高速缓存结构,超标量处理器,流水线,分支预测,乱序执行,微代码转换,协处理器以及其他复杂的优化技术。不知道这些词的意思也没关系,要是你想深入了解CPU,可以在互联网上搜索一下这些词。

2.3 几个术语

计算机内存是经过编号的一系列国定大小的存储单元。每个存储单元编号称为该存储单元的地址。一个存储单元的大小称为一个字节。在X86处理器上,一个字节对应一个0到255之间的数字。
你可能想知道为什么计算机只能存储0到255之间的数,却能显示和处理文本,图形,和更大的数?首先,显卡等专用硬件对每个数字都有专门的解释。当在屏幕上显示数字时,计算机根据ascii码表蒋你发送给它的没一个 0到255之间的数字转换为显示在屏幕上的字符,每个数字对应一个字母或10以内的数字。例如,大写字母A用数字65表示,而数字1则用49表示。因此,要在屏幕上打印出“HELLO”,你实际上要把数字序列49,48,48发给计算机。
对于程序员来说,数字不仅能表示ascii字符,还能表示你希望它表示的任何信息。例如,如果我经营着一家商店,那我会用一个数字代表一件商品,每个这样的数字实际上都对应另一串数字,也就是扫描此商品时会显示的那串ascii码。另外,我还希望用更多的数字表示价格,库存等信息。
那么,如果我们需要大于255的数字该怎么办呢?很简单,只要把多个字节组合起来,就可以表示较大的数字了。这样,两个字节就可以表示0到65536之间的任何数字,四字节就能表示0到4294967295之间的任何数字。然而,写程序的时候要通过组合字节来增大数字颇为困难,这需要运用一点数学知识。幸运的是,计算机能帮我们表示多达4字节的数值。事实上,4字节长的数值也是默认情况下我们要使用的数值长度。
前面提到过,计算机除了有常规内存外,还有称为寄存器的特殊单元。寄存器是计算机运算时用到的元件。可将寄存器设想为你书桌上的一块空间,用于临时存放正在使用的东西。你可能把许多资料放在文件夹和抽屉中,但当前正在使用的东西总是放在桌上。寄存器就是用来保存你当前正在操作的数字内容。
在我们当前使用的计算机上,每个寄存器的大小为4各字节。计算器中典型寄存器的大小称为计算机的字长。x86处理器的字长为4个字节。这意味着,x86计算机能一次计算4字节,大约可以表示40亿个值。
地址的长度也是4个字节(一个字长),因此也适合放入寄存器。这一方面意味着,如果计算机的内存足够大,x86处理器可以访问多达4294967296字节的内存;另一方面,也意味着我们可以用存储数字的方式来存储地址。事实上,电脑不会区分地址值,数字值,ascii码值或其他什么值。当你要显示数字时,它就是一个ascii码值;当你要寻找数字指向的字节时,它就变成了地址。花点时间好好理解上述内容,这对于理解计算机程序如何工作至关重要。
由于地址中存储的值并不是常规值,而是指向内存中不同位置的数字,因此存储在内存中的地址也称为指针。
正如我们前面所说的,计算机指令也存储在内存中。事实上,由于指令与数据在内存中的存储方式完全相同,计算机知道某个存储单元时指令的唯一途径,就是有被称为指令指针的专用寄存器在某个时刻指向它。如果指令指针指向一个内存字节,该字节就作为一条指令被加载。除了这种途径以外,计算机无法区别程序和其他类型的数据。

2.4 内存详解

计算机的精确要求程序员做到同样的精确。作为一台机器,计算机并不知道你的程序要做到什么。因此,你告诉计算机做什么,它才会做什么。如果你不小心输出一个常规的数值,而不是构成数值中每个数字的ascii码,计算机将不会阻止你,而你的屏幕上将会出现一串乱码(计算机会尝试寻找此数值对应的ascii码,然后打印出来)。如果你让计算机从包含数据而非程序指令的存储单元开始执行指令,那么就没人知道计算机会对此如何解释,经管它肯定会尝试解释。总之,计算机会完全以你指定的顺序执行你的指令即使这样做毫无意义。
可见,计算机会忠诚地执行你的指令,无论它是否有意义。因此,作为程序员,你要确切的知道数据在内存中是如何放置的。由于计算机只能存储数字,因此文字,图片,网页,音乐,文件和其他内容在计算机中都只是长串的数字,只有对应的特定程序知道如何解释它们。
例如,如果你想将客户信息存储在内存中,那么一种方式是首先设置客户姓名和地址所要占用的最大空间量(如各占50个ascii字符,即50字节)。然后用数字表示客户的年龄和用户ID。采用这种存储方式,一条客户记录占据的内存空间将如下所示:

记录开始处:
客户姓名(50个字节)-记录起始处
客户地址(50个字节)-记录起始处+50字节
客户年龄(1个字- 4字节)- 记录起始处+100字节
客户ID号(1个字- 4字节)- 记录起始处+104字节

在这种存储方式中,只要给出客户记录的起始地址,你就能知道该客户的其他数据在哪里。但该存储方式将客户姓名和地址限制在50个ascii字符以内,有可能无法满足实际需求。
如何消除这样的限制呢?我们可以采用另一种存储方式:用指针直指向记录中的信息。例如,可以用指向客户姓名的指针取代客户姓名字段。采用这种存储方式,一条客户记录在内存中将如下所示:

记录开始处:
客户姓名(1个字)-记录起始处
客户地址(1个字)-记录起始处+4字节
客户年龄(1个字)- 记录起始处+8字节
客户ID号(1个字)- 记录起始处+12字节

而实际的客户姓名和地址将存储在内存中的其他位置。采用上述方式,我们不仅易于确定每条记录的每部分数据相对记录起始处的位置,而且无需限制客户姓名和地址所占用的空间。不论采取上面哪种存储方式,如果记录中字段的长度是可变的,那就没办法知道下一字段是从何处开始的了;因为字段可变造成每条记录的长短又会有所不同,所以要找到下一条记录的开始位置更是难上加难。因此,几乎所有记录都是固定长度的。长度可变的数据通常与记录的其余部分分开存储。

2.5 寻址方式

计算机处理器有多种不同的数据访问方式,它们被称为寻址方式。最简单的寻址方式是立即寻址方式,在这种寻址方式下,指令本身即包含要访问的数据。例如,如果我们想把寄存器初始化为0,那么可以使用立即寻址方式,在指令中直接给出数字0,而不是告诉计算机要到那个地址去读0。
在寄存器寻址方式中,指令中包含要访问的寄存器,而不是内存位置。
除了上述两种寻址方式外,其余寻址方式都与地址有关。
在直接寻址方式中,指令中包含要访问的内存地址,例如,直接寻址的指令可能为:请将地址2002中的数据加载到这个寄存器。按照这个指令,计算机将直接读取字节编号为2002的内存中的内容,并将其复制到寄存器。
在变址寻址方式中,指令中除包含一个要访问的内存地址外,还要指定一个变址寄存器。如果该变址寄存器包含数字4,那么实际用于加载数据的地址就是2006。利用这种寻址方式,如果你有起始位置为2002的一组数字,那就可以使用变址寄存器循环提取每个数字。在x86处理器中,还可以指定变址的比例因子,这样就能以一次一个字节或一个字(四个字节)的方式访问内存。比如,如果你正在访问一个完整的字,那么变址需要乘以4(即比例因子是4)才能得到第四个字相同当前地址的确切位置。如果你想访问从内存地址2002开始的第四个字节,因为是一次访问一个字节,你要把3加载到变址寄存器(我们从0开始计数),并设置比例因子为1。这样,你就会得到位置2005的数据。但如果你访问从2002开始第四个字的位置,就把3加载到变址寄存器,并设置比例因子为4,结果是从位置为2014(即第四个字的起始位置)加载数据。花点时间认真验算一下,确保你切实了解上述工作原理。
在间接寻址方式下,指令中包含一个寄存器,该寄存器中存储的是指向要访问数据的指针。比如,如果我们使用间接寻址方式,并指定值为4的%eax寄存器,则表示我们要使用内存地址位置4中的值。同样是这个指令,在直接寻址中,我们将只加载值4;但在间接寻址中,我们会用4作为地址去寻找数据。
最后,还有一种基址寻址方式。这种方式与间接寻址类似。在本书中,我们将主要使用这种寻址方式。
在2.4杰中,我们曾讨论过存储客户信息的内存结构的例子。下面我们以此为例来解释基址寻址方式,设想我们想访问某客户的年龄,也就是其记录的第八个字节的数据,而寄存器中存放着客户信息的起始内存地址。我们可以使用基址指针寻址,指定寄存器为基址指针,8为偏移量。这与变址寻址相似,不同之处在于:在基址指针寻址中,偏移量是常数,指针被保存在寄存器中;而在变址寻址中,偏移量存储在寄存器中,而指针是常量。
此外还存在其他寻址方式,但上面这些是最重要的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值