为什么说汇编语言的执行效率高? (为什么说汇编语言指令比机器语言指令更容易理解和记忆)

本文目录导航:

为什么说汇编语言的执行效率高?

呵呵,你这个问题倒是很有意思。

你说的是没错,所有语言编写的代码最终要运行,都要转化成机器码。

但是,由于这个“转化”所采用的方法不同,其所需要消耗的时间也使不同的。

具个简单的例子来说,比如把一个变量的值自加1,并执行100次,也就是下面这条语句:for(i=0;i<100;){i++;}那么对于一个没有充分优化的C语言编译器而言,你需要每次寻址内存找到变量,然后把变量值拷贝到寄存器,然后对寄存器自加1,然后把寄存器值写回到内存,整个过程需要反复执行100次。

但是如果你写汇编代码,那就没这么麻烦了,你只需要寻址内存一次,把变量读入寄存器,然后对寄存器自加100次,最后写回内存即可。

你可以想见,这个汇编代码的执行速度要比C语言快得多,但它们所执行的功能是一样的。

当然,我前面这个例子只是用来说明问题,并不具有实践价值。

实践中有很多因素影响程序的效率,例如编译方式、优化程度等等。

而这些与程序员的素质也有关系,一个差的汇编程序很可能不如一个好的C语言程序执行效率高。

为什么说汇编语言的执行效率高? (为什么说汇编语言指令比机器语言指令更容易理解和记忆)

如何在C语言中嵌入汇编?

在 Visual C++ 中使用内联汇编- -使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤。

在 Visual C++ 中,内联汇编是内置的编译器,因此不需要配置诸如 MASM 一类的独立汇编工具。

这里,我们就以 Visual Studio 2003 为背景,介绍在 Visual C++ 中使用内联汇的相关知识(如果是早期的版本,可能会有些许出入)。

内联汇编代码可以使用 C/C++ 变量和函数,因此它能非常容易地整合到 C/C++ 代码中。

它能做一些对于单独使用 C/C++ 来说非常笨重或不可能完成的任务。

一、 优点 使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤。

在 Visual C++ 中,内联汇编是内置的编译器,因此不需要配置诸如 MASM 一类的独立汇编工具。

这里,我们就以 Visual Studio 2003 为背景,介绍在 Visual C++ 中使用内联汇的相关知识(如果是早期的版本,可能会有些许出入)。

内联汇编代码可以使用 C/C++ 变量和函数,因此它能非常容易地整合到 C/C++ 代码中。

它能做一些对于单独使用 C/C++ 来说非常笨重或不可能完成的任务。

内联汇编的用途包括:使用汇编语言编写特定的函数; 编写对速度要求非常较高的代码; 在设备驱动程序中直接访问硬件; 编写 naked 函数的初始化和结束代码。

二、 关键字使用内联汇编要用到 __asm 关键字,它可以出现在任何允许 C/C++ 语句出现的地方。

我们来看一些例子:简单的 __asm 块:__asm{ MOV AL, 2MOV DX, 0xD007OUT AL, DX}在每条汇编指令之前加 __asm 关键字:__asm MOV AL, 2__asm MOV DX, 0xD007__asm OUT AL, DX因为 __asm 关键字是语句分隔符,所以可以把多条汇编指令放在同一行:__asm MOV AL, 2__asm MOV DX, 0xD007__asm OUT AL, DX显然,第一种方法与 C/C++ 的风格很一致,并且把汇编代码和 C/C++ 代码清楚地分开,还避免了重复输入 __asm 关键字,因此推荐使用第一种方法。

不像在 C/C++ 中的{ },__asm 块的{ }不会影响 C/C++ 变量的作用范围。

同时,__asm 块可以嵌套,而且嵌套也不会影响变量的作用范围。

为了与低版本的 Visual C++ 兼容,_asm 和 __asm 具有相同的意义。

另外,Visual C++ 支持标准 C++ 的 asm 关键字,但是它不会生成任何指令,它的作用仅限于使编译器不会出现编译错误。

要使用内联汇编,必须使用 __asm 而不是 asm 关键字。

三、 汇编语言1. 指令集内联汇编支持 Intel Pentium 4 和 AMD Athlon 的所有指令。

更多其它处理器的指令可以通过 _EMIT 伪指令来创建(_EMIT 伪指令说明见下文)。

2. MASM 表达式在内联汇编代码中,可以使用所有的 MASM 表达式(MASM 表达式是指用来计算一个数值或一个地址的操作符和操作数的组合)。

3. 数据指示符和操作符虽然 __asm 块中允许使用 C/C++ 的数据类型和对象,但它不能使用 MASM 指示符和操作符来定义数据对象。

这里特别指出,__asm 块中不允许 MASM 中的定义指示符(DB、DW、DD、DQ、DT 和 DF),也不允许使用 DUP 和 THIS 操作符。

MASM 中的结构和记录也不再有效,内联汇编不接受 STRUC、RECORD、WIDTH 或者 MASK。

4. EVEN 和 ALIGN 指示符尽管内联汇编不支持大多数 MASM 指示符,但它支持 EVEN 和 ALIGN。

当需要的时候,这些指示符在汇编代码里面加入 NOP 指令(空操作)使标号对齐到特定边界。

这样可以使某些处理器取指令时具有更高的效率。

5. MASM 宏指示符内联汇编不是宏汇编,不能使用 MASM 宏指示符(MACRO、REPT、IRC、IRP 和 ENDM)和宏操作符(<>、!、&、% 和 )。

6. 段必须使用寄存器而不是名称来指明段(段名称_TEXT是无效的)。

并且,段跨越必须显式地说明,如 ES:[EBX]。

7. 类型和变量大小在内联汇编中,可以用 LENGTH、SIZE 和 TYPE 来获取 C/C++ 变量和类型的大大小。

* LENGTH 操作符用来取得 C/C++ 中数组的元素个数(如果不是一个数组,则结果为 1)。

* SIZE 操作符可以获取 C/C++ 变量的大小(一个变量的大小是 LENGTH 和 TYPE 的乘积)。

* TYPE 操作符可以返回 C/C++ 类型和变量的大小(如果变量是一个数组,它得到的是数组中单个元素的大小)。

例如,程序中定义了一个 8 维的整数型变量:int iArray[8];下面是 C 和汇编表达式中得到的 iArray 及其元素的相关值:__asm C SizeLENGTH iArray sizeof(iArray)/sizeof(iArray[0])8SIZE iArray sizeof(iArray)32TYPE iArray sizeof(iArray[0]) 48. 注释内联汇编中可以使用汇编语言的注释,即;。

例如:__asm MOV EAX, OFFSET pbBuff; Load address of pbBuff因为 C/C++ 宏将会展开到一个逻辑行中,为了避免在宏中使用汇编语言注释带来的混乱,内联汇编也允许使用 C/C++ 风格的注释。

9. _EMIT 伪指令_EMIT 伪指令相当于 MASM 中的 DB,但是 _EMIT 一次只能在当前代码段( 段)中定义一个字节。

例如:__asm{ JMP _CodeLabel_EMIT 0x00; 定义混合在代码段的数据_EMIT 0x01_CodeLabel: ; 这里是代码_EMIT 0x90; NOP指令}10. 寄存器使用一般来说,不能假定某个寄存器在 __asm 块开始的时候有已知的值。

寄存器的值将不能保证会从 __asm 块保留到另外一个 __asm 块中。

如果一个函数声明为 __fastcall 调用方式,则其参数将通过寄存器而不是堆栈来传递。

这将会使 __asm 块产生问题,因为函数无法被告知哪个参数在哪个寄存器中。

如果函数接收了 EAX 中的参数并立即储存一个值到 EAX 中的话,原来的参数将丢失掉。

另外,在所有声明为 __fastcall 的函数中,ECX 寄存器是必须一直保留的。

为了避免以上的冲突,包含 __asm 块的函数不要声明为 __fastcall 调用方式。

提示:如果使用 EAX、EBX、ECX、EDX、ESI 和 EDI 寄存器,你不需要保存它。

但如果你用到了 DS、SS、SP、BP 和标志寄存器,那就应该用 PUSH 保存这些寄存器。

提示:如果程序中改变了用于 STD 和 CLD 的方向标志,必须将其恢复到原来的值。

四、 使用 C/C++ 元素1. 可用的 C/C++ 元素C/C++ 与汇编语言可以混合使用,在内联汇编中可以使用 C/C++ 变量以及很多其它的 C/C++ 元素,包括:符号,包括标号、变量和函数名; 常量,包括符号常量和枚举型成员; 宏定义和预处理指示符; 注释,包括/**/和//; 类型名,包括所有 MASM 中合法的类型; typedef 名称,通常使用 PTR 和 TYPE 操作符,或者使用指定的的结构或枚举成员。

在内联汇编中,可以使用 C/C++ 或汇编语言的基数计数法。

例如,0x100 和 100H 是相等的。

2. 操作符使用内联汇编中不能使用诸如<<一类的 C/C++ 操作符。

但是,C/C++ 和 MASM 共有的操作符(比如*和[]操作符),都被认为是汇编语言的操作符,是可以使用的。

举个例子:int iArray[10];__asm MOV iArray[6], BX ; Store BX at iArray + 6 (Not scaled)iArray[6] = 0;// Store 0 at iArray+12 (Scaled)提示:在内联汇编中,可以使用 TYPE 操作符使其与 C/C++ 一致。

比如,下面两条语句是一样的:__asm MOV iArray[6 * TYPE int], 0 ; Store 0 at iArray + 12iArray[6] = 0;// Store 0 at iArray + 123. C/C++ 符号使用在 __asm 块中可以引用所有在作用范围内的 C/C++ 符号,包括变量名称、函数名称和标号。

但是不能访问 C++ 类的成员函数。

下面是在内联汇编中使用 C/C++ 符号的一些限制:每条汇编语句只能包含一个 C/C++ 符号。

在一条汇编指令中,多个符号只能出现在 LENGTH、TYPE 或 SIZE 表达式中。

在 __asm 块中引用函数必须先声明。

否则,编译器将不能区别 __asm 块中的函数名和标号。

在 __asm 块中不能使用对于 MASM 来说是保留字的 C/C++ 符号(不区分大小写)。

MASM 保留字包含指令名称(如 PUSH)和寄存器名称(如 ESI)等。

在 __asm 块中不能识别结构和联合标签。

4. 访问 C/C++ 中的数据内联汇编的一个非常大的方便之处是它可以使用名称来引用 C/C++ 变量。

例如,如果 C/C++ 变量 iVar 在作用范围内:__asm MOV EAX, iVar; Stores the value of iVar in EAX如果 C/C++ 中的类、结构或者枚举成员具有唯一的名称,则在 __asm 块中可以只通过成员名称来访问(省略.操作符之前的变量名或 typedef 名称)。

然而,如果成员不是唯一的,你必须在.操作符之前加上变量名或 typedef 名称。

例如,下面的两个结构都具有 SameName 这个成员变量:struct FIRST_TYPE{ char *pszWeasel;int SameName;};struct SECOND_TYPE{ int iWonton;long SameName;};如果按下面方式声明变量:struct FIRST_TYPE ftTest;struct SECOND_TYPE stTemp;那么,所有引用 SameName 成员的地方都必须使用变量名,因为 SameName 不是唯一的。

另外,由于上面的 pszWeasel 变量具有唯一的名称,你可以仅仅使用它的成员名称来引用它:__asm{ MOV EBX, OFFSET ftTestMOV ECX, [EBX] ; 必须使用ftTestMOV ESI, [EBX]. pszWeasel ; 可以省略ftTest}提示:省略变量名仅仅是为了书写代码方便,生成的汇编指令还是一样的。

5. 用内联汇编写函数如果用内联汇编写函数的话,要传递参数和返回一个值都是非常容易的。

看下面的例子,比较一下用独立汇编和内联汇编写的函数:; ; Compute the power of an integerPUBLICGetPowerAsm_TEXT SEGMENT WORD PUBLIC CODEGetPowerAsm PROCPUSHEBP ; Save EBPMOV EBP, ESP; Move ESP into EBP so we can refer; to arguments on the stackMOV EAX, [EBP+4]; Get first argumentMOV ECX, [EBP+6]; Get second argumentSHL EAX, CL ; EAX = EAX * (2 ^ CL)POP EBP ; Restore EBPRET ; Return with sum in EAXGetPowerAsm ENDP_TEXT ENDSENDC/C++ 函数一般用堆栈来传递参数,所以上面的函数中需要通过堆栈位置来访问它的参数(在 MASM 或其它一些汇编工具中,也允许通过名称来访问堆栈参数和局部堆栈变量)。

下面的程序是使用内联汇编写的:// PowerC.c#include int GetPowerC(int iNum, int iPower);int main(){ printf(3 times 2 to the power of 5 is %d\n, GetPowerC( 3, 5));}int GetPowerC(int iNum, int iPower){ __asm{ MOV EAX, iNum ; Get first argumentMOV ECX, iPower ; Get second argumentSHL EAX, CL ; EAX = EAX * (2 to the power of CL)}// Return with result in EAX}使用内联汇编写的 GetPowerC 函数可以通过参数名称来引用它的参数。

由于 GetPowerC 函数没有执行 C 的 return 语句,所以编译器会给出一个警告信息,我们可以通过 #pragma warning 禁止生成这个警告。

内联汇编的其中一个用途是编写 naked 函数的初始化和结束代码。

对于一般的函数,编译器会自动帮我们生成函数的初始化(构建参数指针和分配局部变量等)和结束代码(平衡堆栈和返回一个值等)。

使用内联汇编,我们可以自己编写干干净净的函数。

当然,此时我们必须自己动手做一些有关函数初始化和扫尾的工作。

例如:void __declspec(naked) MyNakedFunction(){ // Naked functions must provide their own prolog.__asm{ PUSH EBPMOV ESP, EBPSUB ESP, __LOCAL_SIZE}...// And we must provide epilog.__asm{ POP EBPRET}}6. 调用 C/C++ 函数内联汇编中调用声明为 __cdecl 方式(默认)的 C/C++ 函数必须由调用者清除参数堆栈,下面是一个调用 C/C++ 函数例子:#include char szFormat[] = %s %s\n;char szHello[] = Hello;char szWorld[] = world;void main(){ __asm{ MOV EAX, OFFSET szWorldPUSHEAXMOV EAX, OFFSET szHelloPUSHEAXMOV EAX, OFFSET szFormatPUSHEAXCALLprintf// 压入了 3 个参数在堆栈中,调用函数之后要调整堆栈ADD ESP, 12}}提示:参数是按从右往左的顺序压入堆栈的。

如果调用 __stdcall 方式的函数,则不需要自己清除堆栈。

因为这种函数的返回指令是 RET n,会自动清除堆栈。

大多数 Windows API 函数均为 __stdcall 调用方式(仅除 wsprintf 等几个之外),下面是一个调用 MessageBox 函数的例子:#include TCHAR g_tszAppName[] = TEXT(API Test);void main(){ TCHAR tszHello[] = TEXT(Hello, world!);__asm{ PUSHMB_OK OR MB_ICONINFORMATIONPUSHOFFSET g_tszAppName ; 全局变量用 OFFSETLEA EAX, tszHello ; 局部变量用 LEAPUSHEAXPUSH0CALLDWORD PTR [MessageBox]; 注意这里不是 CALL MessageBox,而是调用重定位过的函数地址}}提示:可以不受限制地访问 C++ 成员变量,但是不能访问 C++ 的成员函数。

7. 定义 __asm 块为 C/C++ 宏使用C/C++ 宏可以方便地把汇编代码插入到源代码中。

但是这其中需要额外地注意,因为宏将会扩展到一个逻辑行中。

为了不会出现问题,请按以下规则编写宏:使用花括号把 __asm 块包围住; 把 __asm 关键字放在每条汇编指令之前; 使用经典 C 风格的注释(/* comment */),不要使用汇编风格的注释(; comment)或单行的 C/C++ 注释(// comment);举个例子,下面定义了一个简单的宏:#define PORTIO __asm\/* Port output */ \{\__asm MOV AL, 2 \__asm MOV DX, 0xD007\__asm OUT DX, AL\}乍一看来,后面的三个 __asm 关键字好像是多余的。

其实它们是需要的,因为宏将被扩展到一个单行中:__asm /* Port output */ { __asm MOV AL, 2__asm MOV DX, 0xD007__asm OUT DX, AL }从扩展后的代码中可以看出,第三个和第四个 __asm 关键字是必须的(作为语句分隔符)。

在 __asm 块中,只有 __asm 关键字和换行符会被认为是语句分隔符,又因为定义为宏的一个语句块会被认为是一个逻辑行,所以必须在每条指令之前使用 __asm 关键字。

括号也是需要的,如果省略了它,编译器将不知道汇编代码在哪里结束,__asm 块后面的 C/C++ 语句看起来会被认为是汇编指令。

同样是由于宏展开的原因,汇编风格的注释(; comment)和单行的 C/C++ 注释(// commen)也可能会出现错误。

为了避免这些错误,在定义 __asm 块为宏时请使用经典 C 风格的注释(/* comment */)。

和 C/C++ 宏一样 __asm 块写的宏也可以拥有参数。

和 C/C++ 宏不一样的是,__asm 宏不能返回一个值,因此,不能使用这种宏作为 C/C++ 表达式。

不要不加选择地调用这种类型的宏。

比如,在声明为 __fastcall 的函数中调用汇编语言宏可能会导致不可预料的结果(请参看前文的说明)。

8. 转跳可以在 C/C++ 里面使用 goto 转跳到 __asm 块中的标号处,也可以在 __asm 块中转跳到 __asm 块里面或外面的标号处。

__asm 块内的标号是不区分大小写的(指令、指示符等也是不区分大小写的)。

例如:void MyFunction(){ goto C_Dest;/* 正确 */goto c_dest;/* 错误 */goto A_Dest;/* 正确 */goto a_dest;/* 正确 */__asm{ JMP C_Dest; 正确JMP c_dest; 错误JMP A_Dest; 正确JMP a_dest; 正确a_dest: ; __asm 标号}C_Dest: /* C/C++ 标号 */return;}不要使用函数名称当作标号,否则将转跳到函数中执行,而不是标号处。

例如,由于 exit 是 C/C++ 的函数,下面的转跳将不会到 exit 标号处:; 错误:使用函数名作为标号JNE :...美元符号$用于指定当前指令位置,常用于条件跳转中,例如:JNE $+5 ; 下面这条指令的长度是 5 个字节JMP _LabelNOP ; $+5,转跳到了这里..._Label:...五、在 Visual C++ 工程中使用独立汇编内联汇编代码不易于移植,如果你的程序打算在不同类型的机器(比如 x86 和 Alpha)上运行,你可能需要在不同的模块中使用特定的机器代码。

这时候你可以使用 MASM(Microsoft Macro Assembler),因为 MASM 支持更多方便的宏指令和数据指示符。

这里简单介绍一下在 Visual Studio 2003 中调用 MASM 编译独立汇编文件的步骤。

在 Visual C++ 工程中,添加按 MASM 的要求编写的 文件。

在解决方案资源管理器中,右击这个文件,选择属性菜单项,在属性对话框中,点击自定义生成步骤,设置如下项目:命令行 /nologo /c /coff -Fo$(IntDir)\$(InputName) $(InputPath)输出:$(IntDir)\$(InputName)如果要生成调试信息,可以在命令行中加入/Zi参数,还可以根据需要生成 和 文件。

如果要在汇编文件中调用 Windows API,可以从网上下载 MASM32 包(包含了 MASM 汇编工具、非常完整的 Windows API 头文件/库文件、实用宏以及大量的 Win32 汇编例子等)。

相应地,应该在命令行中加入/I X:\MASM32\INCLUDE参数指定 Windows API 汇编头文件()的路径。

MASM32 的主页是:,里面可以下载最新版本的 MASM32 包。

学习嵌入式开发需要学习ARM汇编语言吗?

学习嵌入式开发基本不需要学习ARM汇编语言,原因如下:1、C语言的发展已经成熟,其编译器编译出的代码文件无论从体积、执行效率都已直逼汇编语言。

2、ARM汇编语言编程本身并不难,由于数据宽度扩展,代码效率更高,执行速度更快,但维护和移植却比C语言程序困难得多。

3、ARM单片机动辄上百个寄存器,虽然功能强大,但却给操作和记忆带来极大挑战,使得用汇编语言编程比51困难许多,编程效率更为低下。

4、汇编语言编程越来越小众化,这就使得可利用资源少,可参考借鉴的资料很少,凡事靠自己,困难不易克服。

当然,如果有时间有精力有能力学习ARM汇编语言,会比不会好。

你可能想看:
« 上一篇
下一篇 »

相关推荐

FreeBasic:强大免费开源的64位basic编译器及应用优势

2025年05月10日

82阅读

脊柱侧弯矫正方法大盘点:运动康复、支具矫正等多种方式

2025年05月09日

84阅读

如何在winForm标题栏去掉标题文字且任务栏图标正常显示

2025年05月09日

83阅读

十一黄金周促销广告词大全,国庆购物狂欢等你来

2025年05月09日

77阅读

关闭快速编辑模式及插入模式,设置控制台指定文字颜色举例

2025年05月09日

62阅读

黄金搭档低糖高钙饮品包装及成分功效全解析,适合人群要知晓

2025年05月09日

90阅读