《深入理解计算机系统》读书笔记-03

《深入理解计算机系统》读书笔记-03

一个有用的例程

书的第 31 页给出了一段例程,用以打印 C 语言中变量对应内存的内容。其具体代码如下:

#include 


typedef unsigned char *byte_pointer;

void show_bytes(byte_pointer start, size_t len)
{
    size_t i;
    for(i = 0; i < len; i++)
        printf(" %.2x", start[i]);
    printf("\n");
}

这一段代码具有很强的实用性,尤其在需要深入查看内存内容的时候。虽说 VS 也有查看内存内容的功能,但 VS 缺点在于太过臃肿,除了做大型项目一般也很少打开;相反这样一段精简的 C 程序更能在各种情况下发挥作用。

一个数学符号

在书的第 36 页第一次出现了一个奇怪的数学符号: ≐ \doteq

经网络查询得知,该符号与 ≈ \approx 是等价的,也就是说是“约等于”的意思,但再看看书中的内容,总是不太对劲儿,“位向量 a ≐ [ 01101001 ] a\doteq[01101001] a[01101001]”难道表示的是“位向量 a a a 约等于位模式 [ 01101001 ] [01101001] [01101001]”吗?

到了第 44 页,终于给出了对这个奇怪数学符号的官方定义:符号“ ≐ \doteq ”表示左边被定义为等于右边。也就是说,“位向量 a ≐ [ 01101001 ] a\doteq[01101001] a[01101001]”实际的含义应该是“位向量 a a a 被定义为位模式 [ 01101001 ] [01101001] [01101001] ”。

异或

异或是布尔代数中的加法

将“异或”与“加法”联系起来的思路让我豁然开朗。以前从来没有想到过还可以以加法的视角来看待异或运算,虽说经常会看到底层的加法运算电路使用异或门来完成相应操作,但也只是以为是人为设计的缘故,并没有思考过为什么都是“异或”。直到在树上看到这样的说法,才终于发现自己以前被逻辑运算和算术运算的条条框框限制住了,哪怕我一向自认是比较善于发散思维的人,也不曾尝试过将逻辑运算和算术运算的内部原理联系起来。

到这里,其实给我更多震动的还不是异或的神奇,而是又一次发现了数学之美。数学的美有很多种,这种深藏于原理内部的相似性就是其中一种。

名字

异或的中文我一直理解为“相异则或”,也就是说参与异或运算的两个元素如果相异,则进行“或”运算;而其英文exclusive or我则一直理解为“互斥型或运算”。也不知是否靠谱,不过在我自己的逻辑中能够自洽。

有趣的性质

  • 异或运算一个有趣的性质是 a ∧ a = 0 a\wedge a=0 aa=0
  • 异或具有交换律
  • 异或具有结合律

综合以上三条性质,可以解决一个十分有趣的问题:在一堆数中,除了一个数只出现奇数次外,其他数都出现了偶数次,问如何找出这个出现了奇数次的数。

运用异或运算,答案很简单,将所有数按位进行异或,最后得到的结果所表示的数,就是要找的出现奇数次的数。简化表示就是: ( a ∧ b ) ∧ a = b (a\wedge b)\wedge a=b (ab)a=b

补码

概念详解

在书中,补码由下述公式定义:
B 2 T w ( x ⃗ ) ≐ − x w − 1 2 w − 1 + ∑ i = 0 w − 2 x i 2 i B 2 T_{w}(\vec{x}) \doteq-x_{w-1} 2^{w-1}+\sum_{i=0}^{w-2} x_{i} 2^{i} B2Tw(x )xw12w1+i=0w2xi2i
但是这样的定义并不直观,因此我想介绍另一个更加直观一些的理解。

在《计算机是怎样跑起来的》一书中对补码的定义十分精到和巧妙,正是看了那一段,我才真正地理解了补码到底是怎么来的。

《计算机是怎样跑起来的》中,定义补码的计算为:将对应正数的原码逐位取反,再在结果的基础上加 1,所得即为对应负数的补码表示。

这一点与惯常的说法没有什么不同,但点睛之笔在于说出了为什么要这样计算。就是看了这一段,我才真正明白为什么补码的计算方式会这么奇怪不自然,甚至有点“造作”。实际上一切的不合理都有一个合理的解释。

下面我用自己的理解尽量清楚地解释一下。

在学习数字电路的时候讲过三种编码:原码、反码和补码,但我从来没有理解到过补码的公式是怎么来的,为什么要叫“补码”这么奇怪的名字。原码容易理解,就是“原始的二进制编码”。反码也容易理解,就是在原码的基础上,按位取反。但是这个“补码”到底是什么意思呢?什么叫补?怎么补?跟谁补?

实际上,“补码”之所以叫“补码”,并不是随意取的名字。其中“补”的意思是“和为零”,也就是说 1 + ( − 1 ) = 0 1+(-1)=0 1+(1)=0,则称 1 和 -1 互补;对二进制数亦然。有符号整数的补码表示正是基于“互补”这个定义而来。

以四位二进制数为例,所能表示的最大无符号数就是位模式 [1111] 对应的无符号整数 15,一旦在此基础上再加 1 想表示 16 的话,就会产生溢出,理论上应该得到的是 [10000],但由于位模式只有四位,因此产生截断变成了 [0000],而 [0000] 对应的无符号数就是整数 0。显然我们可以得到 [ 1111 ] + [ 0001 ] = [ 0000 ] [1111]+[0001]=[0000] [1111]+[0001]=[0000]。好了,根据上面对补码的定义,到这里我们可以得出结论:[1111] 和 [0001] 是互补的,即“互为补码”。

而根据数学常识,“相加为 0 的两个数互为相反数”,也就是说 [1111] 和 [0001] 在这种位有限的情况下,还应该是事实上的相反数。我们将 [0001] 视作正常表示的无符号数 +1,那么自然而然地,[1111] 表示的就应该是 -1 了。

根据这个步骤,我们可以求出每个可表示的正数对应的负数的补码表示。

但有一个问题,知道了一个正数,怎么快速确定其对应负数的补码表示呢?

回忆一下前面提到过的,“对原码按位取反”。显然这个反码与原码之和一定是 [1111],因为按位取反之后,原码是 1 的位,反码一定是 0;原码是 0 的位,补码一定是 1。于是相加之后得到的一定是一个全为 1 的位模式。“再在所得结果的基础上加 1”。众所周知,加法具有交换律。计算过程说要在反码的基础上加 1,我们可以考虑将反码与原码求和之后,再在和的基础上加 1。这样就相当于在 [1111] 的基础上加 1,所得的结果一定是 0,即 [0000]。

好了,讨论到这一步,再回过头来看“互补”的定义:和为零即为互补。于是我们可以说,假设位向量 a ⃗ \vec{a} a 是一个补码表示的正数 a a a 对应的位模式,而 b ⃗ \vec{b} b 则是一个补码表示的负数 b b b 对应的位模式。要使 b = − a b=-a b=a,显然应该有
a ⃗ + b ⃗ = 0 \vec{a}+\vec{b}=0 a +b =0
假如 a ⃗ ′ \vec{a}' a a ⃗ \vec{a} a 对应的反码,则容易有 ​
a ⃗ + a ⃗ ′ + 1 = 0 \vec{a}+\vec{a}'+1=0 a +a +1=0

比较上述两式,可以得到:
b ⃗ = a ⃗ ′ + 1 \vec{b}=\vec{a}'+1 b =a +1
也就是说,从补码的定义,我们可以推导出计算补码的方式:将对应正数的原码逐位取反,再在结果的基础上加 1,所得即为对应负数的补码表示。

以上。

你可能感兴趣的:(计算机基础知识,阅读)