暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

编程到底是在编什么

我们都是小青蛙 2021-09-03
540

小孩子又回来啦!

狗哥的疑惑

狗哥是一位大一新生。

老师在C语言课程上写了一段程序:

    #include <stdio.h>


    int main(void)
    {
    printf("HelloWorld!\n");
    return 0;
    }

    老师说:这是我们学习的第一个C语言程序,这个程序的功能极其简单,就是向显示器上输出一行字符:“Hello World”。

    我们可以打开任意一个文本编辑器,比方说记事本,然后把上述文本复制进去,然后把它以文件的形式保存到硬盘上就算是编程完成了,我们这里可以给这个保存程序的文件任意起个名,比方说就叫helloworld.c
    吧(.c这个扩展名也不是必须的,只是别人看到这个扩展名后可以知道这是一个保存着C语言程序的文件罢了)。

    狗哥说:我在玩儿英雄联盟的时候,都是直接双击一个叫Client.exe
    的程序就可以玩了:

    为啥这次双击这个helloworld.c
    文件后,却打开了文本编辑器?这个用C语言编的helloworld.c
    程序和Client.exe
    不是一种程序吗?是不是我把helloworld.c
    的后缀名改成exe
    就好了呢?

    老师说:不不不,把helloworld.c
    改成helloworld.exe
    仍然不能运行。其实helloworld.c
    Client.exe
    不是一种程序。helloworld.c
    是给人看的程序,本质上就是一个纯文本文件,而Client.exe
    包含若干条机器指令,CPU会读取这些指令并执行它们。

    狗哥说:老师,我刚入学,你说的话有点儿难懂喔,什么是CPU,啥是个机器指令,机器指令是干啥的?

    老师说:那我们现在就不讲C语言程序了,先来看看编程的本质到底是在干什么。

    程序员的视角——存储器

    编程其实就像是在玩游戏。

    你肯定玩过扫雷游戏吧:

    扫雷玩家面对的是许许多多的小格子,其中一些格子下面藏着地雷,有些没有藏,如果你点击某个背后是地雷的小格子的话,就GAME OVER了。

    程序员玩家在玩编程游戏时,面对的也是许许多多的类似的小格子,不过这些小格子并不是用来藏地雷的,是用来存储数据的。比方说下边是一个4行×4列小格子示意图:

    为了很容易的找出某个格子,我们会给每个格子分配一个编号。比方说我们给上图所示的16个格子分别编一个号:

    小贴士:这里需要大家注意一下,在计算机中我们通常都是从0开始编号的。

    习惯上,我们把格子的编号称作该格子的地址
    ,所以上边16个格子的地址分别是0~15

    另外,在画图展示的时候,我们更习惯将这些格子放到一列中:

    这些格子的组合被称作存储器
    (memory,更多的地方翻译成内存,不过小孩子觉得存储器
    更合适)。

    上图中的每一个格子可以被称作存储器的一个存储单元
    。存储单元中存储的数据是二进制形式的,每个存储单元可以存储的二进制位个数也被称作存储单元的大小
    。存储单元的大小是人为规定的,不过现在用的计算机中存储器的存储单元大小一般是8个二进制位,也称作1个字节。

    程序员的任务就是向存储器
    中填入内容,填的内容可以被大致分为两类:

    1.指令 2. 数据

    数据
    好理解,比方说我们班级同学的身高、体重、考试成绩这些统统都可以被称作数据
    。在计算机中,我们这里所说的数据
    本质上数据就是一个二进制数字。由于1个存储单元仅包含8个二进制位,能表示的数字范围太小,如果我们有存储较大数字的需求,可以使用多个存储单元来共同存储1个数字。

    但是指令
    该怎么理解呢?其实指令
    就是用来指挥计算机的硬件设备来做一些事情。除了存储器
    ,计算机的另一个十分重要的硬件设备就是CPU
    ,CPU负责将指令从存储器中读出并执行,做一些诸如加减乘除之类的运算操作。

    在我们深入了解指令
    之前,我们得先了解一下这个CPU对我们程序员有什么影响。

    程序员的视角——CPU

    CPU的主要功能是完成运算,比方说将两个二进制数加起来得到加和。我们把CPU一次处理数据的最大位数称作CPU的位数,也称作一个字(word)。比方说CPU最多支持一次对两个32位的二进制数进行运算,那么我们就把这个CPU称作32位CPU

    虽说CPU内部的硬件电路十分复杂,但那是硬件工程师的事情。对于程序员来说,我们只关注CPU内部的若干寄存器。

    小贴士:寄存器是一种特殊的存储设备,读写寄存器的速度非常快,比我们前边所说的存储器要强很多。

    在32位CPU中,程序员所能使用的每个寄存器的位数也都是32位(即1个寄存器可以存储32个二进制位)。比方说某个CPU中包含16个程序员可以使用的寄存器:

    这16个寄存器分别被命名为R0~R15
    ,这些寄存器的组合也被称作寄存器文件
    (Register File)或者寄存器组
    (Register Set)。

    程序员的任务

    存储器
    (memory)和寄存器文件
    (register file)都属于存储二进制位的设备,对于程序员来说,大家只需要知道它们的两种区别就好了:

    寄存器文件的读写速度非常快,它是CPU内部的存储设备;存储器的读写速度比寄存器文件慢很多,一般在CPU外部单独存在。

    寄存器文件不能做的很大,一般CPU中包含1个、几个、十几个或者几十个供程序员使用的寄存器;存储器
    的容量一般很大,可以存储几十亿甚至上百亿字节的数据。

    下边就该计算机指令登场了!指令本质上也是一串二进制数据,通常可以分为操作码
    操作数
    两个部分:

    其中操作码
    表示该条指令是要完成什么功能,操作数
    用来表示该指令操作的数据。操作码
    是一条指令中必须的,有的指令可以没有操作数,有的指令也可以有多个操作数。

    一条指令占用的存储空间大小可以是固定的,也可以是不固定的。比方说在现实生活中MIPS32的CPU的指令就占用固定的4个字节大小,而Intel公司的x86系列CPU的指令就占用1~15个字节不等。

    CPU提供的指令主要完成下边4个方面的任务:

    加载任务

    简称load:将存储器
    中的数据加载到寄存器文件
    中的某个寄存器中。

    很显然,为完成加载任务
    ,我们需要在指令中至少指定2种操作数:

    要读取存储器中哪个地址的数据。

    要将存储器中的数据加载到哪个寄存器中。

    比方说在MIPS32中,我们经常使用下边的指令来完成任务1

    其中op部分代表操作码,rt部分代表要将数据写入哪个寄存器中。base和offset部分共同决定该指令是要读取哪个CPU地址处的数据。我们详细看一下这些指令的作用:

    lb指令:全称为Load Byte。表示加载一个字节的有符号数据到目的寄存器中。

    小贴士:
    由于MIPS32中的寄存器都是32位的,加载一个字节的数据到寄存器的低8位后,其余高位需要使用符号扩展进行填充。

    lbu指令:全称为Load Byte Unsigned。表示加载一个字节的无符号数据到目的寄存器中。

    lh指令:全称为Load Halfword。表示加载半个字,也就是2个字节的有符号数到目的寄存器中。

    lhu指令:全称为Load Halfword Unsigned。表示加载半个字,也就是2个字节的无符号数到目的寄存器中。

    lw指令:全称为Load Word。表示加载1个字,也就是4个字节的数据到目的寄存器。由于目的寄存器也是4个字节大小,所以就不需要进行高位扩展了。

    写回任务

    简称store:将寄存器文件
    中某个寄存器中的数据写回到存储器
    中。

    比方说MIPS32提供了sb、sbu、sh、slhu、sw等指令。

    跳转任务

    简称jump:完成某个需求可能需要很多条指令,有时需要直接跳转到某条指令执行,或者先判断一下某些条件是否成立,如果成立再跳转到某条指令执行。

    比方说MIPS提供了jr、beq、blez等指令。

    运算任务

    简称operate:对寄存器文件或者存储器中的数据进行运算,结果再写回寄存器文件或存储器中。

    运算任务
    属于CPU最核心的任务,就是进行运算,这里包括算术运算和逻辑运算,我们看一下MIPS32中支持的一些逻辑运算指令:

    我们分别看一下它们的用途:

    and指令用于将rs和rt寄存器中的数据进行与运算,将结果写到rd寄存器中。

    or指令用于将rs和rt寄存器中的数据进行或运算,将结果写到rd寄存器中。

    xor指令用于将rs和rt寄存器中的数据进行异或运算,将结果写到rd寄存器中。

    nor指令用于将rs和rt寄存器中的数据进行同或运算,将结果写到rd寄存器中。

    andi指令用于将16位立即数imm进行0扩展为32位,然后与rs寄存器中的数据进行与运算,将结果写到rd寄存器中。

    ori指令用于将16位立即数imm进行0扩展为32位,然后与rs寄存器中的数据进行或运算,将结果写到rd寄存器中。

    xori指令用于将16位立即数imm进行0扩展为32位,然后与rs寄存器中的数据进行异或运算,将结果写到rd寄存器中。

    小贴士:
    某些CPU,比如x86系列CPU,还支持对寄存器和某个CPU地址对应的数据进行运算的指令;某些指令集体系结构,比如PDP系列,还支持对两个CPU地址对应的数据进行运算的指令。而以MIPS仅支持对两个寄存器中的数据进行运算,这样做的目的是为了让CPU的硬件设计更加简单,单条指令的执行速度也更快。

    程序员任务小结

    我们这里篇幅有限,并不想深入介绍完全每个指令的格式以及它们的用途,大家只需要知道CPU提供了完成load、store、jump和operate四大任务相应的指令,我们只需要将这些指令填充到存储器
    的存储单元中,CPU就可以自动的读取这些指令并执行它们。

    由于存储器中的指令和数据可以随时改变,存储器
    中填入的指令和数据也可以被称作软件
    ,而我们将指令和数据写入存储器中的过程也被称作编程
    ,进行编程的人也被称作程序员
    ,也就是屏幕前的你。

    编译器

    狗哥说:我现在已经大致理解了编程的本质,就是把指令和数据写到存储器中而已,指令用于完成load、store、jump和operate这几项任务。直接写指令就能编程的话,我们为啥还要学文本形式的C语言程序呢?

    老师说:你还记得我们上边说的进行and运算的指令的操作码是啥码?

    狗哥说:不记得了,这个得再返回头看看。

    老师说:是吧,刚讲过就忘了,可见直接使用计算机指令编程序是多么麻烦的一件事情,所以之后有人就写了一个称作编译器
    的软件,它可以读取我们写的文本形式的C语言程序,然后产生CPU可以执行的计算机指令。

    狗哥说:那我明白了,就是我们写的C语言程序需要被翻译翻译。

    最后一个疑惑

    狗哥说:有一个疑惑啊,程序不是以文件的形式存储在硬盘里的么,这个硬盘就是我们上边所说的存储器
    吗?

    老师说:不是的哈,读写硬盘的速度十分缓慢,所以CPU一般不直接从硬盘中读取指令并执行。像硬盘这样的速度超慢,但容量大、价格低的存储设备我们一般将其称作辅助存储器
    (Secondary Memory),简称辅存
    ,或者外存
    。而CPU其实是从所谓的主要存储器
    (Main Memory),简称主存,或者内存
    中读取指令和数据的,你可以把它当作我们平时网上购买的内存条

    狗哥说:那这里又有问题了,既然程序原先是保存在外存
    中的,那怎么把它弄到内存
    呢?

    老师说:你还有完没完,该下课了。这涉及到CPU和I/O设备如何通信的问题,下次再说,拜拜>_>

    小孩子新书雏形

    想了解CPU是怎样运行的同学可以看一下小孩子的掘金小册《计算机是怎样运行的:从根儿上理解计算机》,从初中物理知识出发,带你一步一步搭建计算机。

    扫描下方二维码可跳转到《计算机是怎样运行的:从根儿上理解计算机》

    文章转载自我们都是小青蛙,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论