我是靠谱客的博主 笑点低星星,最近开发中收集的这篇文章主要介绍内嵌汇编,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Inline Assembler
内嵌汇编

以下原文均由微软解释

汇编有多种用途,比如改进程序运行速度,减少内存,控制硬件等。内嵌汇编机制使程序员能够在C 或C++ 源程序中直接嵌入汇编指令,不需要另外的汇编编译和链接步骤。内嵌汇编的编译由C 编译器实现,所以不必使用其它的汇编编译器。
注意:使用内嵌汇编后,程序不能保证在所有硬件平台上正常使用。内嵌汇编不包括MASM 所有的宏与数据指令。为跨平台程序编写的内嵌汇编应该为每个平台编写代码。

因为内嵌汇编不需要外部汇编编译器,所以使用起来更方便些。内嵌汇编代码可以使用C 或C++ 的变量和函数命名,更容易与程序整合到一起。内嵌汇编代码可以与C 或C++ 语句混合使用,解决高级语言不容易实现的工作更为容易。

__asm 关键字定义汇编语句,它可以出现在任何C 或C++ 语句合法的位置。关键字后面必须跟汇编指令或一对花括号。语法:
__asm mov ebp, esp
__asm {}
__asm {
      mov ebp, esp
      }
__asm mov ebp, esp __asm sub esp, 0X50
不使用花括号,表示__asm 关键字后面是一条汇编语句,使用花括号,表示花括号里面每一行表示一条汇编语句。为向上兼容,_asm 关键字与__asm 同意。__asm 关键字是一个语句分隔符,可以将几条语句放在同一行,如上所示。与标准C 或C++ 不同的是,__asm 关键字不影响变量使用范围,关键字可以嵌套使用,嵌套后也不影响变量使用范围。

使用内嵌汇编主要有几个目的:
1. 编写特定函数
2. 优化时间敏感的代码
3. 直接硬件设备访问
4. 为无保护(Naked 函数, 如void __declspec(naked) main())编译进出堆栈代码

注意:Visual C++ 使用__asm 关键字,标准C++ 使用的关键字是asm。在VC++ 中使用asm 关键字编译器不会产生错误,但也不会产生任何有意义的实际代码。

在__asm 关键字中使用汇编语言
内嵌汇编与其它汇编语言使用相似,如可以使用MASM 的合法表达式。这里主要讨论其不同之处。
指令集:VC++ 编译器支持直至Pentium 4 和AMD Athlon 处理器的所有指令助记符。可以使用_emit Pseudoinstruction 创建其它指定的处理器指令集。
表达式:内嵌汇编代码中可以使用MASM 中所有结果为单一值或单一地址的表达式。
伪指令和操作符:内嵌汇编不能通过伪指令或操作符定义数据对象,它能够使用C 或C++ 定义的数据类型和对象。DB, DW, DD, DQ, DT, DF 伪指令,DUP, THIS 操作符,以及MASM 结构和记录都不能使用,STRUC, RECORD, WIDTH, MASK伪指令均不被接受。
EVEN 和ALIGN 伪指令:虽然内嵌汇编不支持大多数MASM 伪指令,但它支持EVEN 和ALIGN 伪指令。编译时会在需要处使用NOP 指令填充地址,以使标签或边界对齐。这样做使某些处理器运行效率更高。
MASM 宏伪指令:内嵌汇编不能使用MASM 宏处理伪指令和宏操作符(MACRO, REPT, IRC, IRP, ENDM, <>, !, &, %, .TYPE)。但内嵌汇编语句块内可以使用C 预处理伪指令。
引用段:内嵌汇编不能使用段名(或者叫做Section Name,如_TEXT),必须显示的引用段寄存器。如ES:[BX]。
变量类型和大小:LENGTH, SIZE, TYPE 操作符不能用于定义新的数据对象,但它可以在内嵌汇编中用于取得C 或C++ 变量的尺寸或类型。LENGTH 返回数组中元素的个数,非数组变量返回1;SIZE 返回C 或C++ 变量的字节尺寸,变量是数组返回整个数组占用的字节数;TYPE 返回C 或C++ 类型或者变量的字节尺寸,如果变量是数组,返回数组中单个元素的尺寸。
注释:内嵌汇编的汇编语句后用用分号,再添加注释。也可以使用C 风格注释。因为C 宏展在一行展开,所以在内嵌汇编中使用的C 宏不要使用注释。
_emit 伪指令:其功能类似于MASM 中的DB 伪指令,在当前文本形成的代码位置处,定义一个字节。一条伪指令同时只能定义一个字节,而且只能定义在代码段中。语法类似于INT 指令。下例在代码中定义字节:
#define randasm __asm _emit 0x4A __asm _emit 0x43 __asm _emit 0x4B
 .
 .
 .
__asm {
     randasm
     }
调试与列表:编译时使用/Zi 选项,内嵌的汇编代码可以被源码级调试器调试。在调试器中,可以在C/C++ 或内嵌汇编语句行上设置段点。如果使用了汇编和源代码混合模式,在调试器中可以后到源代码和反汇编后的内嵌汇编。在同一行内使用多个内嵌汇编分隔符(__asm)可能妨碍调试,在源码模式下,不能在一行中有多个语句的行上设置断点。在内嵌汇编中使用C 宏也会产生同样的问题。编译时使用/FAs 选项创建源码与汇编混合的列表时,列表中包括每个内嵌汇编语句行的源码和编译后代码。宏在列表中不展开,在实际编译过程中宏是一直展开的。
Intel MMX 指令集:内嵌汇编中可以使用MMX 多媒体指令集。调试器反汇编时也支持。编译器发现在有MMX 指令后没有EMMS 指令时会产生警告信息。

内嵌汇编中的C 或C++
因为内嵌汇编指令可以和C 或C++ 语句混合编程,所以能够使用C/C++ 变量名和其它许多元素,有如下可用元素:
1. 符号,包括标签、变量和函数名
2. 常量,包括符号常量和enum 枚举成员
3. 宏和预处理伪指令
4. 注释(/* */ 和// 均可)
5. 类型名(只要MASM 类型合法)
6. typedef 名,通常与PTR 和TYPE 等操作符一同使用或者指定结构或联合中的成员
在内嵌汇编中,整形常量可以C 风格或汇编风格,如0x100 和100h 相同,使用八进制整型常量时用0 打头,如0777 表示八进制常量。
下面是一些其它说明:
内嵌汇编中的操作符:内嵌汇编中不能使用C/C++ 操作符,如<< 操作符。C 和MASM 共同使用的操作符,如*,都被当做汇编操作符,比如,方括号[] 在C 中表示数组下标,而在内嵌汇编中认为是索引操作符,表示字节偏移量。例:
int array[10];
__asm mov array[6], bx ;  Store BX at array+6 (not scaled)
__asm mov array[6 * TYPE int], 0 ; Store 0 at array + 24
array[6] = 0;         /* Store 0 at array+24 (scaled) */
内嵌汇编中使用C/C++ 符号:虽然可以在命名使用范围内使用C/C++ 定义的符号(变量名、函数名、标签,不是符号常量和枚举成员,不能调用C++ 成员函数),但有些限制:每个内嵌汇编语句只能引用一个C/C++ 符号,多个符号只能出现在LENGTH, TYPE, SIZE表示式中;内嵌汇编中引用的函数必有前向定义原型,否则编译器不能区别是否是函数名或标签;内嵌汇编块中不能引用与MASM 保留字相同拼写的符号;内嵌汇编中不能识别结构和联合标记。
内嵌汇编中使用C/C++ 数据:最方便之处是能直接引用C/C++ 变量名,类、结构或联合的成员名是唯一的,那么内嵌汇编可以直接引用成员名,不必在点(.)前缀操作符前使用变量名或typedef 名,如果成员名不是唯一的,那么必须在点前缀操作符前用变量或typedef 名,例如:
// InlineAssembler_Accessing_C_asm_Blocks.cpp
struct first_type
{
   char *weasel;
   int same_name;
};
struct second_type
{
   int wonton;
   long same_name;
};

int main()
{
   struct first_type hal;
   struct second_type oat;
   __asm
   {
      // mov ebx, OFFSET hal
      mov ecx, [ebx]hal.same_name ; Must use 'hal'
      mov esi, [ebx].weasel       ; Can omit 'hal'
   }
}
内嵌汇编编写C 函数:如下例用内嵌汇编编写函数:
int power2( int num, int power )
{
   __asm
   {
      mov eax, num    ; Get first argument
      mov ecx, power  ; Get second argument
      shl eax, cl     ; EAX = EAX * ( 2 to the power of CL )
   }
   /* Return with result in EAX */
}
参数也是通过堆栈传递的,与MASM 编写函数相比简化了堆栈的计算。上例中函数返回值保存在EAX 中,可以省略return 语句,但编译时会给出警告,用#paragma warning 关闭这个警告信息。

内嵌汇编中保存和使用寄存器
通常情况下,可以不关心内嵌汇编中的寄存器使用。不能保证两个内嵌汇编块之间的寄存器内容不变化。如果使用了__fastcall 函数调用协定,编译器通过寄存器传递函数参数,而不是通过堆栈,这时在使用内嵌汇编编写函数时就存在问题,因为不知道哪个寄存可能会传递哪个参数。另外,在__fastcall 协定时必须要保存ECX 寄存器。应该在使用内嵌汇编编写函数时尽量避免使用__fastcall 调用协定,如果编译器使用了/Gr 选项指定全局使用__fastcall 调用格式,那么就要在每个内嵌汇编函数前声明__cdecl 或__stdcall。
用内嵌汇编编写C/C++ 函数时,不必保存的寄存器是EAX, EBX, ECX, EDX, ESI 和EDI。如果使用了EBX,ESI 或EDI 寄存器,那么编译器会在函数调用和返回时保存这些寄存器的内容。如果改变了方向寄存器标志STD/CLD 设置,那么就要保存标志寄存器。其它段寄存器,SP,BP 如果被改变都需要进行保存。

内嵌汇编中的跳转
内嵌汇编指令或者C/C++ 的goto 语句可以任意跳到同一范围__asm 块内部或外部的标签。__asm  内部定义的标签不是大小写敏感的,而C/C++ 标签只在goto 语句跳转时表现为敏感。因为内嵌汇编块内不是大小写敏感的,所以不要用C/C++ 关键字、保留字或者库函数名作为标签,如:使用exit作为标签,那么跳转时可能跳到exit 函数,而不是标签位置。$ 符号仍作为当前地址计数器。

内嵌汇编中调用C 函数
内嵌汇编中可以调用C 函数,包括C 库函数。下例相当于printf(format, hello, world);:
// InlineAssembler_Calling_C_Functions_in_Inline_Assembly.cpp
#include <stdio.h>
char format[] = "%s %s/n";
char hello[] = "Hello";
char world[] = "world";
int main( void )
{
   __asm
   {
      mov  eax, offset world
      push eax
      mov  eax, offset hello
      push eax
      mov  eax, offset format
      push eax
      call printf
      //clean up the stack so that main can exit cleanly
      //use the unused register ebx to do the cleanup
      pop  ebx
      pop  ebx
      pop  ebx
   }
}
因为函数调用有不同的参数传递顺序和协议,所以要注意压栈顺序和清除堆栈。

内嵌汇编中调用C++ 函数
内嵌汇编只能调用没有被重载的全局C++ 函数。调用重载全局C++ 函数或C++ 成员函数,编译器将给出错误。

用C 宏定义内嵌汇编块(略)

优化内嵌汇编
有三种优化影响:首先编译器不再优化内嵌汇编代码;第二因为影响寄存器使用,编译器尽量不在跨内嵌汇编处使用不受影响的寄存器;最后某些函数范围的优化可能影响函数内的内嵌汇编。


转载请注明出处,谢谢
马明丰 2007.09.10

最后

以上就是笑点低星星为你收集整理的内嵌汇编的全部内容,希望文章能够帮你解决内嵌汇编所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部