使用纸带编写计算机程序
计算机并不认识高级语言,因此需要将高级语言翻译成机器语言,CPU才能执行。机器语言就是一串数字。我们用纸带的孔来代表 0 和 1。这样我们就可以使用纸带表示一条条机器指令,让 CPU 去执行。接下来看一个例子。
//test.c
int main(){
int a = 1;
int b = 2;
a += b;
}
这是一段非常简单的 C 程序。首先我们需要知道这段程序的机器指令,可以借助 gcc 和 objdump 这两个工具帮助我们把这段程序的汇编代码和机器码打印出来。
gcc -g -c test.c
objdump -d -M MIPS -S test.o
执行上面两条指令后,可以在控制台看到下面的内容
test.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
int main(){
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
int a = 1;
8: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
int b = 2;
f: c7 45 fc 02 00 00 00 movl $0x2,-0x4(%rbp)
a += b;
16: 8b 45 fc mov -0x4(%rbp),%eax
19: 01 45 f8 add %eax,-0x8(%rbp)
1c: b8 00 00 00 00 mov $0x0,%eax
}
21: 5d pop %rbp
22: c3 retq
不同的 CPU 有不同的指令集,对应着不同的汇编语言和不同的机器码。上面我们是使用了 MIPS 指令集,MIPS 是一组有 MIPS 技术公司在 80 年代中期设计出来的 CPU 指令集,现在已经开源了。
指令类型 | 6位 | 5位 | 5位 | 5位 | 5位 | 6位 | 解释 |
---|---|---|---|---|---|---|---|
R | opcode | rs | rt | rd | shamt位移量 | funct功能码 | 算术操作、逻辑运算 |
I | opcode | rs | rt | address/immediate 地址/立即数 | 数据传输、条件分支、立即数操作 | ||
J | opcode | taget address 目标地址 | 无条件跳转 |
MIPS 指令是一个 32 位的整数,高 6 位叫操作码(Opcode),也就是代表这条指令具体是一条什么样的指令,剩下的26位有三种格式,分别是 R、I 和 J。
R 指令一般用来做算术和逻辑运算,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。
I 指令,则通常是用在数据传输、条件分支以及在运算的时候使用的并非变量而是常数的时候。这个时候,没有位移量和操作码,不需要第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。
J 指令就是跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。
看下面的例子
add $t0,$s2,$s1
这是 R 型指令,对应的 opcode 为 0。rs 代表第一个寄存器 s1 的地址是 17。rt 代表第二个寄存器 s2 的地址是 18。rd代表目标的临时寄存器 t0 的地址,是 8。因为不是位移操作,所以位移量是 0。把这些数字拼在一起,就是一条 MIPS 的加法指令。
指令 | 格式 | opcode | rs | rt | rd | shamt | funct |
---|---|---|---|---|---|---|---|
add | R | 0 | 17 | 18 | 8 | 0 | 32 |
二进制 | 000000 | 10001 | 10010 | 01000 | 00000 | 100000 | |
十六进制 | 0X02324020 |
现在对这个十六进制的指令在纸带上编程,我们约定,打孔表示 1,未打孔表示0。
这样就可以使用纸带打孔编程了。