第十八篇 -- 在C++中嵌入汇编语言
基于C++宝典的学习
一、什么是汇编语言
汇编语言是一种功能很强的程序设计语言,也是利用了计算机所有硬件特性并能直接控制硬件的语言。在汇编语言中,用助记符(Memoni)代替操作码,用地址符号(Symbol)或标号(Label)代替地址码。这样用符号代替机器语言的二进制码,就把机器语言变成了汇编语言。
汇编语言比机器语言易于读写、调试和修改,同时也具有机器语言执行速度快、占用内存空间少等优点。但在编写复杂程序时,相对高级语言来说汇编语言代码量较大,而且汇编语言依赖于具体的机型,不能通用,因此不能直接在不同处理机型之间移植。虽然其移植性不好,但效率非常高,针对计算机特定硬件而编制的汇编语言程序,能准确地发挥计算机硬件的功能和特长,程序精炼而质量高,所以汇编语言至今仍是一种常用而强有力的底层开发语言。
二、汇编语言的特点
汇编语言指令使用一些具有相应含义的助忆符来表达的,所以,它要比机器语言容易掌握和运用。但因为要直接使用CPU资源,所以相对高级程序设计语言来说它又显得相对复杂。汇编语言程序归纳起来大概有以下几个主要特点。
1. 与硬件相关:汇编语言指令是指机器指令的一种符号表示,而不同类型的CPU有不同的机器指令系统,也就有不同的汇编语言,所以汇编语言程序与机器有着密切的关系。也就是说,不同型号的CPU之间是无法通用相同汇编代码的,因此导致汇编语言的移植性和通用性降低,这是汇编语言天生的缺陷。
2. 保持了机器语言的优点,具有直接和简捷的特点:正因为汇编语言有“与机器相关性”的特性,程序员用汇编语言编写程序时,可充分发挥自己的聪明才智,对机器内部的各种资源进行合理的安排,让它们始终处于最佳的使用状态,这样做的最终效果就是程序的执行代码短,执行速度快,所以,汇编语言是高效的程序设计语言。另外汇编语言可有效地访问、控制计算机的各种硬件设备,如磁盘、存储器、CPU、I/O端口等,实现资源利用的最大化。
3. 编写程序复杂:汇编语言是一种面向机器的语言,其汇编指令与机器指令基本上一一对应,所以,汇编指令也同机器指令一样既有功能单一、具体的特点。要想完成某件工作,就必须安排CPU的每步工作。另外,在编写汇编语言程序时,还要考虑具体机型的限制、汇编指令的细节和限制等。
4. 经常与高级语言配合使用,应用十分广泛:在某些情况下,比如直接操作CPU执行中断以实现线程调度、保存CPU寄存器以存储/恢复线程状态等,仅仅使用高级语言是完不成的,需要借助于汇编语言,但是仅使用汇编语言的话,大型程序恐怕需要付出比高级语言几倍的工作量,有时候也是没有必要的。因此,可以在高级语言里嵌入汇编语句,让仅仅一部分需要高效率的代码用汇编语言来完成,其余的框架搭建等用高级语言来完成,这样既保证了效率又降低了代码的复杂程度。这种配合使用在大型软件开发里经常遇到,应用十分广泛。
三、汇编语言的应用领域
汇编语言是面向机器的低级程序设计语言。它可以直接控制硬件的最下层,如寄存器、标志位、存储单元等,因而能充分发挥机器硬件的性能,具有其他高级语言不可替代的作用。汇编语言是计算机能够提供给用户的最快的、最有效的语言,也是能够利用计算机所有硬件特性并且能够直接控制硬件的唯一语言。也正因为汇编语言具有这些特性,在对于程序的控件和时间要求很高的场合,以及需要直接控制硬件的应用场合,使用汇编语言是必不可少的,它能够完成许多其他高级语言所无法完成的功能。比如Linux操作系统的内核,大多数代码是用C语言完成的,但是在某些关键的与硬件关系密切的部分,仍不可避免地使用了汇编语言。汇编语言试用的应用领域如下。
1. 要求执行效率高、反应快的领域:在操作系统内核、实时系统、智能仪器仪表的控制程序等领域中最主要的要求是效率高和反应快。汇编语言是直接作用于机器硬件的,它所设计的程序具有较小的时间复杂度和空间复杂度。而高级语言是面向问题来设计的,它所设计的程序运行时间长,并且占用存储空间大,在反应和效率上远远不如汇编语言。
2. 与硬件资源密切相关的软件开发领域:汇编语言是面向机器的,而高级语言是面向问题的,对于一些与硬件资源密切相关的软件也只能采用汇编语言。如外部设备的底层驱动程序、单片机控制、图像处理软件、加密解密算法等领域。
3. 受存储容量限制的应用领域:诸如家用电器的控制领域等,控制系统功能单一、行为简单,且受内存、CPU等硬件条件限制,需要使用汇编语言进行嵌入式控制。
不过,相比高级语言,汇编语言调试困难,工作量大,其不宜使用的领域有大型软件的整体开发、没有特殊要求的一般应用系统的开发等。
四、汇编语言的基本语法
下面以8086系统为例讲解汇编语言的基本语法和用法。
按功能分,8086系统的汇编指令包括数据传送指令、算数指令、逻辑指令、串处理指令、控制转移指令和处理机控制指令6大类。
》通用数据传送指令
8086系统有4类数据传送指令,以实现CPU的内部寄存器之间、CPU与存储器之间、CPU和I/O端口之间的数据传送。这4类指令是通用数据传送指令、累加器专用传送指令、地址传送指令和标志传送指令。通用数据传送指令中包括最基本的传送MOV(Move)指令、堆栈PUSH(Push onto the stack) 和POP(Pop from the stack) 指令、数据交换XCHG(Exchange)指令。
1. 最基本的传送指令MOV指令
MOV指令为双操作数指令,它将一个字节或一个字的操作数从源操作数复制至目的操作数,其语法格式为:
MOV DST,SRC
其中,SRC为源操作数,它可以是立即数、寄存器以及各种寻址的内存单元内容。DST为目的操作数,它可以是寄存器或者各种寻址的内存单元,不可以是立即数。
执行的操作:
(DST)<-(SRC)
对于最基本的传送指令的使用,有几点需要说明:
1. 目的数可以是通用寄存器、存储单元和段寄存器(但不允许用CS段寄存器)。
2. 立即数不能直接传送至段寄存器。
3. 不允许在两个存储单元间直接传送数据。
4. 不允许在两个段寄存器间直接传达信息。
以8086为例:
MOV CL, AL ;AL中的8位数据到CL
MOV DS, AX ;AX中的16位数据到DS
2. 堆栈操作指令
堆栈是按照后进先出(LIFO--Last In First Out) 原则组织的一段内存区域。在子程序调用和终端处理时,分别要保存返回地址和断点地址,在进入子程序和中断服务程序后,还通常需要保护通用寄存器原先的内容,子程序返回或中断处理返回主程序前,则要恢复通用寄存器原先的内容,并分别将返回地址或断点地址恢复到指令指针寄存器中。这些功能都要通过堆栈来实现。另外,在高级语言中除中断及子程序调用外,参数的传递也是靠堆栈来实现的。除返回地址和断点地址的保护及恢复操作是由CPU自动完成的以外,寄存器的保存、恢复以及参数的传递都需要由堆栈指令来完成。
堆栈指令包括入栈PUSH指令和出栈POP指令。
PUSH是入栈指令,完成将源操作数中的一个字推入(也称压入)堆栈的操作。其堆栈指针SP的值始终指向刚刚入栈的数据处,每进一个字,栈顶指针SP的值减2.其语法格式为:
PUSH SRC
其中SRC为源操作数,可以是除立即数之外的16位的寄存器或者内存字单元的内容(两个字节)。对于入栈PUSH指令的使用,需要说明的是:
1. 入栈的操作数除不允许用立即操作数外,可以为通用寄存器、段寄存器(全部)和存储器。
2. 入栈时高位字节先入栈,低位字节后入栈。
以8086为例:
PUSH AX ;CPU通用寄存器入栈
PUSH CS ;段寄存器入栈
PUSH [BX+DI] ;存储器单元入栈
POP是出栈指令,用于将SP所指的堆栈顶处的一个字取出(也称弹出),送至目的DST中,并且SP的值加2.其语法格式为:
POP DST
DST是目的操作数,可以是除立即数之外的16位的寄存器或者内存字单元的内容(两个字节)。对于出栈POP指令的使用,需要说明的是:
1. 出栈操作数除不允许用立即数和CS段寄存器外,可以为通用寄存器、段寄存器和存储器。
2. 执行POP SS指令后,堆栈区在存储区的位置要改变。
执行POP SP指令后,栈顶的位置要改变。
以8086为例:
POP AX ;CPU通用寄存器出栈
POP CS ;段寄存器出栈
POP [BX+DI] ;存储单元出栈
3.数据交换指令:XCHG
数据交换指令XCHG可在寄存器之间、寄存器与存储器之间进行数据交换,但不允许在两个存储单元之间执行交换过程,并且段寄存器和IP寄存器不能参与数据交换。此外,该指令的执行不影响标志位。其语法格式为:
XCHG OPR1, OPR2
上述数据交换指令实现操作数OPR1和OPR2中的一个字或一个字节的数据交换。对于数据交换指令的使用,需要说明的是:
1. 必须有一个操作数在寄存器中。
2. 不能与段寄存器交换数据。
3. 存储器与存储器之间不能交换数据。
以8086为例:
XCHG AL, BL ;AL和BL间进行字节交换
XCHG BX, CX ;BX与CX间进行字交换
》累加器专用传送指令
累加器专用传送指令包括I/O端口向CPU输入信息指令IN(Input)、CPU向端口发送信息指令OUT(Output)以及换码指令XLAT(Translate)。
在IBM PC机里,所有I/O端口与CPU之间的通信都由IN和OUT指令来完成。其中IN完成从I/O到CPU的信息传送,而OUT完成从CPU到I/O的信息传送。CPU只能用累加器(AL或AX)接收或发送消息。
1. 输入IN指令
输入指令完成将信息从I/O通过累加器传送到CPU的操作。该指令有长格式和短格式之分。长格式语法格式为:
IN AL, PORT(每次输入一字节内容。字节传送)
IN AX, PORT(字传送)
执行的操作
(AL)<-(PORT)(字节)
(AX)<-(PORT+1, PORT)(字)
短格式语法格式为:
IN AL, DX(字节)
IN AX, DX(字)
执行的操作:
AL<-((DX))(字节)
AX<-((DX)+1, DX)(字)
CPU与外设通信时,输入/输出通信必须经过特殊的端口进行。外部设备最多可有65536个端口,端口号(即外设的端口地址)为0000~FFFFh。其中前256个端口(0~FFH)可以直接在指令中指定,这就是长格式指令中的PORT,此时机器指令用另个字节表示,第二个字节就是端口号。所以用长格式时可以在指令中直接指定端口号,但只限于外设的前256个端口。当端口号大于255时,只能使用短格式指令。短格式指令要用寄存器DX来间接传送,此时DX的内容是端口号,而IN指令的作用是将DX中指示的端口号内的信息送至AL或AX,OUT指令的作用是将AL或AX的内容送至DX中的内容所指示的端口中。当所送内容是一个字时,必须使用连续的两个端口号,端口地址(即DX中的内容)只能取偶数。
总而言之,IN和OUT指令提供了字和字节两种使用方式,选用哪一种,则取决于外设端口宽度。8088是准16位机,外部数据总线是8位,因而只有字节传送指令,而8086还有字传送指令。以8086位例,将12位A/D变换器所得数字量输入,这时,A/D变换器应使用一个字端口,输入数据的程序为:
MOV DX, 02F0H ;设该端口为02F0H
IN AX, DX
2. 输出OUT指令
输出OUT指令完成将信息从CPU通过累加器传送到I/O的操作。与输入IN指令同样的原因,输出OUT指令的语法格式也有长格式和短格式之分。
长格式语法格式为:
OUT PORT, AL(字节)
OUT PORT, AX(字)
执行的操作:
(PORT)<-(AL)(字节)
(PORT+1,PORT)<-(AX)(字)
短格式语法格式为:
OUT DX, AL(字节)
OUT DX, AX(字)
执行的操作:
((DX))<-(AL)(字节)
((DX)+1, (DX))<-AX(字)
以8086为例:
OUT PORT, AL ;直接的字节输出,PORT规定与IN指令相同
OUT PORT, AX
OUT DX, AL ;间接地字节输出
OUT DX, AX
MOV AL, 05H
OUT 27H, AL ;将字节05H传送到地址27H的端口
。。。
其他各种命令不赘述,不会可以查。直接进入汇编语言在C++中的应用
五、汇编语言在C++中的应用
》内联汇编的优点
因为在Visual C++中使用内联汇编不需要额外的编译器和联接器,且可以处理Visual C++中不能处理的一些事情,同时可以使用在C/C++中的变量,所以非常方便。
内联汇编代码不易于移植,如果你的程序打算在不同类型的机器(比如x86和Alpha)上运行,应当尽量避免使用内联汇编,这时可以使用MASM,因为MASM支持更方便的宏指令和数据指示符。
1. __asm语法
__asm关键字用来调用内联汇编,可以出现在任何合法的C或C++声明中。它不能单独出现,后面必须有汇编指令,可以是一条汇编指令、大括号括起来的一组代码,或者至少是大括号括起来的空代码。术语“__asm块”指的是任何单独的一条指令或一组指令,可以不包括在大括号里。
第一种语法格式:
__asm 汇编指令
第二种语法格式:
__asm
{
汇编指令列表
}
例如,下面的代码是一个简单的大括号里的__asm块:
__asm
{
mov al, 4
mov dx, 0xB008
out dx, al
}
另外,在每一条汇编指令前加上__asm,与前面的方法是一样的作用。例如:
__asm mov al, 4
__asm mov dx, 0xB0008
__asm out dx, al
上面的两个例子所生成的代码是相同的,但是在括号里的__asm块这种方式更具优势,因为大括号可以使汇编指令很清楚地和C或C++代码分开,避免了无意义的__asm关键字重复。另外,大括号还可以避免引起歧义。如果想把C或C++代码和__asm块放在同一行,则必须把这个__asm块放在括号里。如果没有括号,编译器就不能确定汇编代码结束和C或C++代码起始的位置。
另外,由于大括号里的语句和一般的MASM语句格式一样,所以可以很方便地从现有的MASM源程序里复制。
不像C或C++中的"{}",__asm块中的"{}"不会影响C或C++变量的作用范围。同时,__asm块可以嵌套,嵌套也不会影响变量的作用范围。
第十八篇 -- 在C++中嵌入汇编语言的更多相关文章
- Egret入门学习日记 --- 第十八篇(书中 8.5~8.7 节 内容)
第十八篇(书中 8.5~8.7 节 内容) 其实语法篇,我感觉没必要写录入到日记里. 我也犹豫了好久,到底要不要录入. 这样,我先读一遍语法篇的所有内容,我觉得值得留下的,我就录入日记里. 不然像昨天 ...
- 第二十八篇:SOUI中自定义控件开发过程
在SOUI中已经提供了大部分常用的控件,但是内置控件不可能满足用户的所有要求,因此一个真实的应用少不得还要做一些自定义控件. 学习一个新东西,最简单的办法就是依葫芦画瓢.事实上在SOUI系统中内置控件 ...
- Python之路【第十八篇】:Web框架们
Python之路[第十八篇]:Web框架们 Python的WEB框架 Bottle Bottle是一个快速.简洁.轻量级的基于WSIG的微型Web框架,此框架只由一个 .py 文件,除了Pytho ...
- 剑指Offer(二十八):数组中出现次数超过一半的数字
剑指Offer(二十八):数组中出现次数超过一半的数字 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn. ...
- Egret入门学习日记 --- 第十九篇(书中 8.8~8.10 节 内容)
第十九篇(书中 8.8~8.10 节 内容) 开始 8.8节. 重点: 1.类型推断. 2.类型强制转换,使其拥有代码提示功能. 3.除了TS自带的类型判断,Egret官方也提供了类型判断的方法. 操 ...
- Egret入门学习日记 --- 第十六篇(书中 6.10~7.3节 内容)
第十六篇(书中 6.10~7.3节 内容) 昨天搞定了6.9节,今天就从6.10节开始. 其实这个蛮简单的. 这是程序员模式. 这是设计师模式. 至此,6.10节 完毕. 开始 6.11节. 有点没营 ...
- Egret入门学习日记 --- 第十五篇(书中 6.1~6.9节 内容)
第十五篇(书中 6.1~6.9节 内容) 好的,昨天完成了第五章. 今天来看第六章. 总结重点: 1.如何对组件进行分组? 跟着做: 重点1:如何对组件进行分组? 首先,选中你想要组合的组件. 然后点 ...
- Egret入门学习日记 --- 第十四篇(书中 5.4~5.6节 内容)
第十四篇(书中 5.4~5.6节 内容) 书中内容: 总结 5.4节 内容重点: 1.如何编写自定义组件? 跟着做: 重点1:如何编写自定义组件? 文中提到了重要的两点. 好,我们来试试看. 第一步, ...
- Egret入门学习日记 --- 第十二篇(书中 5.1节 内容)
第十二篇(书中 5.1节 内容) 昨天把 第4章完成了. 今天来看第5章. 接下来是 5.1节 的内容. 总结一下 5.1节 的重点: 1.如何制作一个公用按钮皮肤. 跟着做: 重点1:如何制作一个公 ...
随机推荐
- robotframework常用关键字
robotframework关键字 可以将关键字看作是处理数据的方法.robotframework的关键字和测试数据组成了测试用例. robotframework关键字包括系统关键字和用户关键字.用户 ...
- MySQL8性能优化
MySQL8.0 引擎: 来看看MySQL8提供的引擎: 常用引擎: InnoDB:支持事务,行级锁,外键,崩溃修复,多版本并发控制:读写效率相对较差,内存使用相对较高,占用数据空间相对较大. MyI ...
- Dynamic Anchor Learning for Arbitrary-Oriented Object Detection(DAL)
面向任意目标检测的动态锚点学习 摘要:面向任意的目标广泛地出现在自然场景.航空照片.遥感图像等中,因此面向任意的目标检测得到了广泛的关注.目前许多旋转探测器使用大量不同方向的锚点来实现与地面真实框的空 ...
- linux基础(电脑基本原理)
1.计算机体系结构:运算器 控制器 存储器 输入设备 输出设备 详解:存储即内存:编址的存储单元.即每一个存储单元在都有一个编址. 控制器告诉运算器加数在存储器的哪个存储单元. POST: ...
- 学习JDK源码(二):Integer
最近没有好好保持学习的好习惯,该打. 天天忙,感觉都不知道在干嘛.真的厌倦了普通的Java代码,还是想学点新技术. 用了这么久的Java,最常用的数据类型肯定是Int了,而他的包装类Integer用的 ...
- Vue项目的开发流程
我先安装的node.js 1.确认已安装了node.js,可在cmd中输入( node -v和npm -v),如显示出版号,说明安装成功 2.安装webpack 和webpack-cli 在全局下安装 ...
- pipenv管理模块和包
pipenv安装 1. 在终端输入:pip install pipenv进行安装 用pipenv创建虚拟环境:pipenv install,在哪个文件下运行这个命令,就是给哪个文件创建虚拟环境 这 ...
- ES6学习笔记之函数(二)
5.作用域 使用默认参数时,参数会形成一个独立的作用域,此作用域与函数体中的作用域是平行关系,互不影响. var x = 1; function show(x, y= function () { x= ...
- PING命令执行漏洞-绕过空格
目录 PING命令执行漏洞-绕过空格 这边介绍一下绕过空格的方法大概有以下几种 方法一:用变量拼接:我们发现源码中有一个$a变量可以覆盖 方法二:过滤bash?那就用sh.sh的大部分脚本都可以在ba ...
- UV贴图类型
凹凸贴图Bump Map.法线贴图Normal Map.高度贴图Height map.漫反射贴图Diffuse Map.高光贴图Specular Map.AO贴图Ambient Occlusion ...