🧩 第三章 理解OOP:编程语言的历史
📄 一句话概括

最早的编程是手写 0 和 1:一次 5+3 要 72 位,写程序的人记的是字节含义,不是意图。

📝 本节和前面几节的关系

3.1 说了必然性,这一节从最原始的一步开始还原——看程序员最早在对付什么。

一、程序曾经是字节

算一次 5 + 3,在 x86 上长这样: B8 05 00 00 00——把 5 放进 eax;83 C0 03——eax 加 3;C3——返回。 实测:3 条指令、9 个字节=72 位。 这 72 位里没有一个字叫【加】,只有【哪个字节做什么】。

二、难在记,不在想

现在的编程难在想清楚要做什么;那时候难在记住怎么做。 83 C0 是加法、89 C3 是搬移——这些全靠背,背错一个字节就是另一条指令。 而且错得不明显:它可能还是一条合法指令,只是干的不是你想的事。 这跟 2.6 讲的企鹅是同一类麻烦:不报错,逻辑错了。

三、地址要自己算

跳转指令里写的是偏移量,不是标签。 E9 F6 FF FF FF 里的 F6 FF FF FF 是【往回跳 10 个字节】——这个数得手算。 中间插一条指令,后面所有跳转的偏移全变。这就是最原始的维护噩梦。

四、这一步留下了什么

留下了两个至今没变的观念:程序就是数据、指令有固定长度和格式。 也留下了一个至今没消失的岗位:反汇编——把字节还原成指令(第 10 章会真的做一次)。 而程序员真正想要的,从第一天起就是【写意图,不写字节】——这就是 3.3 要迈的第一步。

五、动手 Demo:最初使用机器语言编写程序

把 5 + 3 的机器码摊开,数一数它有多少位。 再跟几种写法的字符数比一比。

code = ["B8 05 00 00 00",   # mov eax, 5
        "83 C0 03",         # add eax, 3
        "C3"]               # ret
nbytes = sum(len(c.split()) for c in code)
print("指令数", len(code), "字节数", nbytes, "位数", nbytes * 8)

实跑输出:

--------------------------------------------------------------------
最初使用机器语言编写程序
--------------------------------------------------------------------

  最原始的程序,是**手写一串 0 和 1**。后来的十六进制只是为了少写几个字。
  算一次 `5 + 3`,在 x86 上长这样:

      B8 05 00 00 00     mov  eax, 5
      83 C0 03           add  eax, 3
      C3                 ret

  实测:**3 条指令、9 个字节=72 位。**

  换个角度看这个数字:

  写法           写 5+3 要几个字符
  -------------------------------------
  纯二进制       72 个 0/1
  十六进制       27 个(含空格)
  汇编助记符     约 40 个
  高级语言       7 个(total = a + b)

  **注意:机器码里没有【加】这个概念,只有【哪个字节做什么】。**
  写程序的人必须记住 `83 C0` 是加法、还要自己算跳转地址——
  **一个地址算错,程序就跑到别处去了,而且不报错。**
  这就是 3.2 说的:**最初的编程,难的是记,不是想。**

实测:3 条指令、9 个字节=72 位——这 72 位里没有一个字叫【加】。 写程序的人必须记住 83 C0 是加法,还要自己算跳转偏移;难在记,不在想。

30 秒实验

  1. 查一查 89 C3 和 83 C0 分别是什么指令,感受一下【背字节】的滋味。
  2. 想一想:如果跳转偏移算错了,程序会怎样——是报错还是跑飞。

常见误区

  1. 以为机器码只是【难写】。它真正的问题是错了不报错。
  2. 以为十六进制是另一种语言。它只是二进制的省字写法。
自测三问(点开对答案) 1. `5 + 3` 的机器码有几个字节? —— 9 个字节(B8 05 00 00 00/83 C0 03/C3),合 72 位。 2. 手写机器码最难的地方是什么? —— 难在记:每个字节干什么全靠背,背错一个字节可能就是另一条合法但错误的指令。 3. 跳转指令里写的是什么? —— 偏移量,不是标签;中间插一条指令,后面所有跳转偏移都要重算。

相关笔记