计算机指令、机器码

news2024/10/6 18:20:28

目录

背景

在软硬件接口中,CPU 帮我们做了什么事?

从编译到汇编,代码怎么变成机器码?

解析指令和机器码

总结延伸


背景

上大学的时候,我们系里教 C 语言程序设计的老师说,他们当年学写程序的时候,不像现在这样,都是用一种古老的物理设备,叫作“打孔卡(Punched Card)”。用这种设备写程序,可没法像今天这样,掏出键盘就能打字,而是要先在脑海里或者在纸上写出程序,然后在纸带或者卡片上打洞。这样,要写的程序、要处理的数据,就变成一条条纸带或者一张张卡片,之后再交给当时的计算机去处理。

上世纪 60 年代晚期或 70 年代初期,Arnold Reinold 拍摄的 FORTRAN 计算程序的穿孔卡照片,图片来源

你看这个穿孔纸带是不是有点儿像我们现在考试用的答题卡?那个时候,人们在特定的位置上打洞或者不打洞,来代表“0”或者“1”。

为什么早期的计算机程序要使用打孔卡,而不能像我们现在一样,用 C 或者 Python 这样的高级语言来写呢?原因很简单,因为计算机或者说 CPU 本身,并没有能力理解这些高级语言。即使在 2019 年的今天,我们使用的现代个人计算机,仍然只能处理所谓的“机器码”,也就是一连串的“0”和“1”这样的数字。

那么,我们每天用高级语言的程序,最终是怎么变成一串串“0”和“1”的?这一串串“0”和“1”又是怎么在 CPU 中处理的?今天,我们就来仔细介绍一下,“机器码”和“计算机指令”到底是怎么回事。

在软硬件接口中,CPU 帮我们做了什么事?

我们常说,CPU 就是计算机的大脑。CPU 的全称是 Central Processing Unit,中文是中央处理器。

我们上一节说了,从硬件的角度来看,CPU 就是一个超大规模集成电路,通过电路实现了加法、乘法乃至各种各样的处理逻辑。

如果我们从软件工程师的角度来讲,CPU 就是一个执行各种计算机指令(Instruction Code)的逻辑机器。这里的计算机指令,就好比一门 CPU 能够听得懂的语言,我们也可以把它叫作机器语言(Machine Language)。

不同的 CPU 能够听懂的语言不太一样。比如,我们的个人电脑用的是 Intel 的 CPU,苹果手机用的是 ARM 的 CPU。这两者能听懂的语言就不太一样。类似这样两种 CPU 各自支持的语言,就是两组不同的计算机指令集,英文叫 Instruction Set。这里面的“Set”,其实就是数学上的集合,代表不同的单词、语法。

所以,如果我们在自己电脑上写一个程序,然后把这个程序复制一下,装到自己的手机上,肯定是没办法正常运行的,因为这两者语言不通。而一台电脑上的程序,简单复制一下到另外一台电脑上,通常就能正常运行,因为这两台 CPU 有着相同的指令集,也就是说,它们的语言相通的。

一个计算机程序,不可能只有一条指令,而是由成千上万条指令组成的。但是 CPU 里不能一直放着所有指令,所以计算机程序平时是存储在存储器中的。这种程序指令存储在存储器里面的计算机,我们就叫作存储程序型计算机(Stored-program Computer)。

说到这里,你可能要问了,难道还有不是存储程序型的计算机么?其实,在没有现代计算机之前,有着聪明才智的工程师们,早就发明了一种叫 Plugboard Computer 的计算设备。我把它直译成“插线板计算机”。在一个布满了各种插口和插座的板子上,工程师们用不同的电线来连接不同的插口和插座,从而来完成各种计算任务。下面这个图就是一台 IBM 的 Plugboard,看起来是不是有一股满满的蒸汽朋克范儿?

一台 IBM 的 Plugboard,图片来源

从编译到汇编,代码怎么变成机器码?

了解了计算机指令和计算机指令集,接下来我们来看看,平时编写的代码,到底是怎么变成一条条计算机指令,最后被 CPU 执行的呢?我们拿一小段真实的 C 语言程序来看看。

// test.c
int main()
{
  int a = 1; 
  int b = 2;
  a = a + b;
}

这是一段再简单不过的 C 语言程序,即便你不了解 C 语言,应该也可以看懂。我们给两个变量 a、b 分别赋值 1、2,然后再将 a、b 两个变量中的值加在一起,重新赋值给了 a 整个变量。

要让这段程序在一个 Linux 操作系统上跑起来,我们需要把整个程序翻译成一个汇编语言(ASM,Assembly Language)的程序,这个过程我们一般叫编译(Compile)成汇编代码。

针对汇编代码,我们可以再用汇编器(Assembler)翻译成机器码(Machine Code)。这些机器码由“0”和“1”组成的机器语言表示。这一条条机器码,就是一条条的计算机指令。这样一串串的 16 进制数字,就是我们 CPU 能够真正认识的计算机指令。

在一个 Linux 操作系统上,我们可以简单地使用 gcc 和 objdump 这样两条命令,把对应的汇编代码和机器码都打印出来。

$ gcc -g -c test.c
$ objdump -d -M intel -S test.o

可以看到,左侧有一堆数字,这些就是一条条机器码;右边有一系列的 push、mov、add、pop 等,这些就是对应的汇编代码。一行 C 语言代码,有时候只对应一条机器码和汇编代码,有时候则是对应两条机器码和汇编代码。汇编代码和机器码之间是一一对应的。

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    rbp,rsp
  int a = 1; 
   8:	c7 45 f8 01 00 00 00 	mov    DWORD PTR [rbp-0x8],0x1
  int b = 2;
   f:	c7 45 fc 02 00 00 00 	mov    DWORD PTR [rbp-0x4],0x2
  a = a + b;
  16:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
  19:	01 45 f8             	add    DWORD PTR [rbp-0x8],eax
  1c:	b8 00 00 00 00       	mov    eax,0x0
  21:	5d                   	pop    rbp
  22:	c3                   	ret  

这个时候你可能又要问了,我们实际在用 GCC(GUC 编译器套装,GUI Compiler Collectipon)编译器的时候,可以直接把代码编译成机器码呀,为什么还需要汇编代码呢?原因很简单,你看着那一串数字表示的机器码,是不是摸不着头脑?但是即使你没有学过汇编代码,看的时候多少也能“猜”出一些这些代码的含义。

因为汇编代码其实就是“给程序员看的机器码”,也正因为这样,机器码和汇编代码是一一对应的。我们人类很容易记住 add、mov 这些用英文表示的指令,而 8b 45 f8 这样的指令,由于很难一下子看明白是在干什么,所以会非常难以记忆。尽管早年互联网上到处流传,大神程序员着拿小刀在光盘上刻出操作系统的梗,但是要让你用打孔卡来写个程序,估计浪费的卡片比用上的卡片要多得多。

从高级语言到汇编代码,再到机器码,就是一个日常开发程序,最终变成了 CPU 可以执行的计算机指令的过程。

解析指令和机器码

了解了这个过程,下面我们放大局部,来看看这一行行的汇编代码和机器指令,到底是什么意思。

我们就从平时用的电脑、手机这些设备来说起。这些设备的 CPU 到底有哪些指令呢?这个还真有不少,我们日常用的 Intel CPU,有 2000 条左右的 CPU 指令,实在是太多了,所以我没法一一来给你讲解。不过一般来说,常见的指令可以分成五大类。

第一类是算术类指令。我们的加减乘除,在 CPU 层面,都会变成一条条算术类指令。

第二类是数据传输类指令。给变量赋值、在内存里读写数据,用的都是数据传输类指令。

第三类是逻辑类指令。逻辑上的与或非,都是这一类指令。

第四类是条件分支类指令。日常我们写的“if/else”,其实都是条件分支类指令。

最后一类是无条件跳转指令。写一些大一点的程序,我们常常需要写一些函数或者方法。在调用函数的时候,其实就是发起了一个无条件跳转指令。

你可能一下子记不住,或者对这些指令的含义还不能一下子掌握,这里我画了一个表格,给你举例子说明一下,帮你理解、记忆。

下面我们来看看,汇编器是怎么把对应的汇编代码,翻译成为机器码的。

我们说过,不同的 CPU 有不同的指令集,也就对应着不同的汇编语言和不同的机器码。为了方便你快速理解这个机器码的计算方式,我们选用最简单的 MIPS 指令集,来看看机器码是如何生成的。

MIPS 是一组由 MIPS 技术公司在 80 年代中期设计出来的 CPU 指令集。就在最近,MIPS 公司把整个指令集和芯片架构都完全开源了。想要深入研究 CPU 和指令集的同学,我这里推荐一些资料,你可以自己了解下。

MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),也就是代表这条指令具体是一条什么样的指令,剩下的 26 位有三种格式,分别是 R、I 和 J。

R 指令是一般用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。

I 指令,则通常是用在数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。

J 指令就是一个跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。

add $t0,$s2,$s1

我以一个简单的加法算术指令 add t0,s1, $s2, 为例,给你解释。为了方便,我们下面都用十进制来表示对应的代码。

对应的 MIPS 指令里 opcode 是 0,rs 代表第一个寄存器 s1 的地址是 17,rt 代表第二个寄存器 s2 的地址是 18,rd 代表目标的临时寄存器 t0 的地址,是 8。因为不是位移操作,所以位移量是 0。把这些数字拼在一起,就变成了一个 MIPS 的加法指令。

为了读起来方便,我们一般把对应的二进制数,用 16 进制表示出来。在这里,也就是 0X02324020。这个数字也就是这条指令对应的机器码。

回到开头我们说的打孔带。如果我们用打孔代表 1,没有打孔代表 0,用 4 行 8 列代表一条指令来打一个穿孔纸带,那么这条命令大概就长这样:

总结延伸

只是这整个程序的机器码,不是通过计算机编译出来的,而是由程序员,用人脑“编译”成一张张卡片的。对应的程序,也不是存储在设备里,而是存储成一张打好孔的卡片。但是整个程序运行的逻辑和其他 CPU 的机器语言没有什么分别,也是处理一串“0”和“1”组成的机器码而已。

我们看到了一个 C 语言程序,是怎么被编译成为汇编语言,乃至通过汇编器再翻译成机器码的。

除了 C 这样的编译型的语言之外,不管是 Python 这样的解释型语言,还是 Java 这样使用虚拟机的语言,其实最终都是由不同形式的程序,把我们写好的代码,转换成 CPU 能够理解的机器码来执行的。

只是解释型语言,是通过解释器在程序运行的时候逐句翻译,而 Java 这样使用虚拟机的语言,则是由虚拟机对编译出来的中间代码进行解释,或者即时编译成为机器码来最终执行。

然而,单单理解一条指令是怎么变成机器码的肯定是不够的。接下来的几节,我会深入讲解,包含条件、循环、函数、递归这些语句的完整程序,是怎么在 CPU 里面执行的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1092152.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3.Python-用Python实现MySQL数据库的增删改查

题记 用python实现mysql数据库的增删改查&#xff0c;以下是具体的代码和操作步骤 安装flask模块 pip install flask 安装mysql.connector模块 pip install mysql-connector-python 编写app.py文件 app.py文件如下&#xff1a; 为什么显示不完整&#xff1f;&#xff1f;&am…

【Spring】事务传播机制

事务传播机制 一. 事务传播机制是什么二. 为什么需要事务传播机制三. 事务传播机制有哪些四. Spring 事务传播机制演示1. ⽀持当前事务&#xff08;REQUIRED&#xff09;2. 不支持当前事务&#xff08;REQUIRES_NEW&#xff09;3. 不⽀持当前事务&#xff0c;NEVER 抛异常4. NE…

订单型企业经营分析指标大全(ODOO15/16)

ODOO-ERP搭建完成之后&#xff0c;我们重点是帮客户建立经营分析能力&#xff0c;以下是针对订单型企业的经营分析指标&#xff0c;涵盖业务运营的监控、资产构成、利润、盈亏点计算、资产运营效率等各方面&#xff0c;并且持续完善。 有些企业不重视&#xff0c;觉得自己企业小…

WebAPI+EF连接SQL Server数据库

右击解决方案-添加-新建项目-选择“类库&#xff08;.NET Framework&#xff09;”,新建的项目取名叫WebApi1.EF 添加EF&#xff1a; 新建一个ADO实体数据模型 选择DBFirst 数据源选择MySql 填写数据库地址及账号密码 选择实体框架版本 选择在数据库中的表User 到此配置完成&am…

对称加密和非对称加密以及CA证书

对称加密 对称加密只用到了公匙,这个公匙是消息的发送方和消息的接收方共享的,也就是消息发送方使用这个公匙对消息加密,然后接收方使用公匙对消息解密,最典型的例子比如像 encrypt 加密,比如我们有个 springboot 应用,需要对 application.yml 文件里的密码做加密,我们…

操作系统备考学习 day8 (2.4.1 ~ 2.4.4)

操作系统备考学习 day8 第二章 进程与线程2.4 死锁2.4.1 死锁的基本概念死锁产生的必要条件死锁什么时候会发生死锁的处理策略 2.4.2 死锁预防破坏互斥条件破坏不剥夺条件破坏请求和保持条件破坏循环等待条件 2.4.3 避免死锁银行家算法 2.4.4 死锁的检测和解除死锁的检测死锁的…

faster python之ctypes调用so/dll

目录 .so和.dll文件将go代码编译为动态链接库将C代码编译成动态链接库 ctypes库基础数据类型使用方法基本数据类型数组类型指针类型结构体类型嵌套结构体结构体数组结构体指针结构体指针数组 ctypes加载DLL的方式1. 使用 CDLL 类2. 使用 WinDLL 类&#xff08;Windows特定&…

【LeetCode高频SQL50题-基础版】打卡第8天:第41~45题

文章目录 【LeetCode高频SQL50题-基础版】打卡第8天&#xff1a;第41~45题⛅前言好友申请II&#x1f512;题目&#x1f511;题解 2016年的投资&#x1f512;题目&#x1f511;题解 部门工资前三高的所有员工&#x1f512;题目&#x1f511;题解 修复表中的名字&#x1f512;题目…

深入JTS事务引擎:理论与实践相结合,掌握高效事务管理的秘诀

事务是可靠应用程序的构建块 如果您阅读过任何有关 J2EE 的介绍性文章或者书籍&#xff0c;那么就会发现&#xff0c;只有一小部分资料是专门针对 Java Transaction Service&#xff08;JTS&#xff09;或 Java Transaction API&#xff08;JTA&#xff09;的。这并不是因为 J…

[补题记录] Atcoder Beginner Contest 295(E)

URL&#xff1a;https://atcoder.jp/contests/abc295 目录 E Problem/题意 Thought/思路 Code/代码 E Problem/题意 给定长度为 N 的数组 A。进行如下操作&#xff1a; 若 Ai 0&#xff0c;将 Ai 等概率地变为 1 ~ M 中的任意一个数&#xff1b;对 A 排序&#xff1b; …

SQL及数据库基础知识点总结

一. SQL&#xff08;Structured Query Language&#xff09;&#xff1a; 结构化查询语言。SQL语法不区分关键字的大小写&#xff0c;多条SQL语句必须以&#xff1b;分隔。 二. SQL的作用&#xff1a; SQL可以访问和处理数据库&#xff0c;包括数据的增删改查&#xff08;插…

【实战项目之网页聊天室】

目录 项目背景 需求分析 1.用户管理模块 注册 登录 2.主界面 个人信息模块 会话列表模块 好友列表模块 消息区域模块 消息传输模块 添加好友模块 编写项目 1.创建项目添加依赖 2.配置项目信息 3.功能实现 用户管理模块 个人信息模块 好友列表模块 消息区…

FPmarkets:MT4中Renko图表工具有哪些?怎么用

以下FPmarkets总结的在MT4中使用Renko图表的最有趣的工具&#xff1a; 第一个是KT Renko实时图表指标&#xff0c;这是一个简单的指示器&#xff0c;仅显示砖块&#xff0c;未添加其他元素&#xff0c;因此与其他自定义指标和顾问兼容。 第二个是Renko Live Chart开发人员提供…

Kafka消费者使用案例

本文代码链接&#xff1a;https://download.csdn.net/download/shangjg03/88422633 1.消费者和消费者群组 在 Kafka 中&#xff0c;消费者通常是消费者群组的一部分&#xff0c;多个消费者群组共同读取同一个主题时&#xff0c;彼此之间互不影响。Kafka 之所以要引入消费者群组…

源码选择指南:比较流行的同城外卖跑腿系统解决方案

随着现代生活的快节奏和数字化转型的浪潮&#xff0c;外卖和跑腿服务成为了不可或缺的一部分。不仅在城市&#xff0c;就连小镇和乡村也可以享受到这些便捷的服务。如果您计划开发或改进一个同城外卖和跑腿系统&#xff0c;选择适合的源码解决方案是至关重要的。在本文中&#…

Jenkins 结合 ANT 发送测试报告

目录 全局变量配置 新建任务 插件安装 HTML 报告配置 邮件配置 全局变量配置 点击 ManageJenkins进入Jenkins 管理 点击 Global Tool Configuration 进入全局变量配置 配置 Ant &#xff0c;Name 自己定义一个比较好理解的名称。 去掉 Install automatically 勾选&#x…

毕业设计选题Java+springboot校园新闻资讯系统源码 开题 lw 调试

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

VBA技术资料MF69:添加和删除工作表中的分页符

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

黑马JVM总结(三十三)

&#xff08;1&#xff09;运行期优化-逃逸分析 在运行期间java虚拟机会对我们代码做一些优化&#xff0c;时间会变短&#xff1a; 字节码反复调用&#xff0c;到达一定的阈值&#xff0c;会启用编译器对自己饿吗编译执行&#xff0c;从0层上升为1层C1 C1和C2他俩的区别是解释…

C语言进阶第六课-----------字符分类函数和内存的开辟

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…