C语言中嵌套汇编语言Word格式.docx
- 文档编号:22005611
- 上传时间:2023-02-02
- 格式:DOCX
- 页数:12
- 大小:22.58KB
C语言中嵌套汇编语言Word格式.docx
《C语言中嵌套汇编语言Word格式.docx》由会员分享,可在线阅读,更多相关《C语言中嵌套汇编语言Word格式.docx(12页珍藏版)》请在冰豆网上搜索。
另外,VisualC++支持标准C++的asm关键字,但是它不会生成任何指令,它的作用仅限于使编译器不会出现编译错误。
要使用内联汇编,必须使用__asm而不是asm关键字。
三、汇编语言
1.指令集
内联汇编支持IntelPentium4和AMDAthlon的所有指令。
更多其它处理器的指令可以通过_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)和宏操作符(<
>
、!
、&
、%和.TYPE)。
6.段
必须使用寄存器而不是名称来指明段(段名称"
_TEXT"
是无效的)。
并且,段跨越必须显式地说明,如ES:
[EBX]。
7.类型和变量大小
在内联汇编中,可以用LENGTH、SIZE和TYPE来获取C/C++变量和类型的大大小。
*LENGTH操作符用来取得C/C++中数组的元素个数(如果不是一个数组,则结果为1)。
*SIZE操作符可以获取C/C++变量的大小(一个变量的大小是LENGTH和TYPE的乘积)。
*TYPE操作符可以返回C/C++类型和变量的大小(如果变量是一个数组,它得到的是数组中单个元素的大小)。
例如,程序中定义了一个8维的整数型变量:
intiArray[8];
下面是C和汇编表达式中得到的iArray及其元素的相关值:
__asmCSize
LENGTHiArraysizeof(iArray)/sizeof(iArray[0])8
SIZEiArraysizeof(iArray)32
TYPEiArraysizeof(iArray[0])4
8.注释
内联汇编中可以使用汇编语言的注释,即"
;
"
。
例如:
__asmMOVEAX,OFFSETpbBuff;
LoadaddressofpbBuff
因为C/C++宏将会展开到一个逻辑行中,为了避免在宏中使用汇编语言注释带来的混乱,内联汇编也允许使用C/C++风格的注释。
9._EMIT伪指
_EMIT伪指令相当于MASM中的DB,但是_EMIT一次只能在当前代码段(.text段)中定义一个字节。
JMP_CodeLabel
_EMIT0x00;
定义混合在代码段的数据
_EMIT0x01
_CodeLabel:
;
这里是代码
_EMIT0x90;
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共有的操作符(比如"
*"
[]"
操作符),都被认为是汇编语言的操作符,是可以使用的。
举个例子:
intiArray[10];
__asmMOViArray[6],BX;
StoreBXatiArray+6(Notscaled)
iArray[6]=0;
//Store0atiArray+12(Scaled)
在内联汇编中,可以使用TYPE操作符使其与C/C++一致。
比如,下面两条语句是一样的:
__asmMOViArray[6*TYPEint],0;
Store0atiArray+12
//Store0atiArray+12
3.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在作用范围内:
__asmMOVEAX,iVar;
StoresthevalueofiVarinEAX
如果C/C++中的类、结构或者枚举成员具有唯一的名称,则在__asm块中可以只通过成员名称来访问(省略"
."
操作符之前的变量名或typedef名称)。
然而,如果成员不是唯一的,你必须在"
操作符之前加上变量名或typedef名称。
例如,下面的两个结构都具有SameName这个成员变量:
structFIRST_TYPE
char*pszWeasel;
intSameName;
};
structSECOND_TYPE
intiWonton;
longSameName;
如果按下面方式声明变量:
structFIRST_TYPEftTest;
structSECOND_TYPEstTemp;
那么,所有引用SameName成员的地方都必须使用变量名,因为SameName不是唯一的。
另外,由于上面的pszWeasel变量具有唯一的名称,你可以仅仅使用它的成员名称来引用它:
MOVEBX,OFFSETftTest
MOVECX,[EBX]ftTest.SameName;
必须使用"
ftTest"
MOVESI,[EBX].pszWeasel;
可以省略"
省略变量名仅仅是为了书写代码方便,生成的汇编指令还是一样的。
5.用内联汇编写函数
如果用内联汇编写函数的话,要传递参数和返回一个值都是非常容易的。
看下面的例子,比较一下用独立汇编和内联汇编写的函数:
PowerAsm.asm
Computethepowerofaninteger
PUBLICGetPowerAsm
_TEXTSEGMENTWORDPUBLIC'
CODE'
GetPowerAsmPROC
PUSHEBP;
SaveEBP
MOVEBP,ESP;
MoveESPintoEBPsowecanrefer
toargumentsonthestack
MOVEAX,[EBP+4];
Getfirstargument
MOVECX,[EBP+6];
Getsecondargument
SHLEAX,CL;
EAX=EAX*(2^CL)
POPEBP;
RestoreEBP
RET;
ReturnwithsuminEAX
GetPowerAsmENDP
_TEXTENDS
END
C/C++函数一般用堆栈来传递参数,所以上面的函数中需要通过堆栈位置来访问它的参数(在MASM或其它一些汇编工具中,也允许通过名称来访问堆栈参数和局部堆栈变量)。
下面的程序是使用内联汇编写的:
//PowerC.c
#include
intGetPowerC(intiNum,intiPower);
intmain()
printf("
3times2tothepowerof5is%d\n"
GetPowerC(3,5));
intGetPowerC(intiNum,intiPower)
MOVEAX,iNum;
MOVECX,iPower;
EAX=EAX*(2tothepowerofCL)
//ReturnwithresultinEAX
使用内联汇编写的GetPowerC函数可以通过参数名称来引用它的参数。
由于GetPowerC函数没有执行C的return语句,所以编译器会给出一个警告信息,我们可以通过#pragmawarning禁止生成这个警告。
内联汇编的其中一个用途是编写naked函数的初始化和结束代码。
对于一般的函数,编译器会自动帮我们生成函数的初始化(构建参数指针和分配局部变量等)和结束代码(平衡堆栈和返回一个值等)。
使用内联汇编,我们可以自己编写干干净净的函数。
当然,此时我们必须自己动手做一些有关函数初始化和扫尾的工作。
void__declspec(naked)MyNakedFunction()
//Nakedfunctionsmustprovidetheirownprolog.
PUSHEBP
MOVESP,EBP
SUBESP,__LOCAL_SIZE
//Andwemustprovideepilog.
POPEBP
RET
6.调用C/C++函数
内联汇编中调用声明为__cdecl方式(默认)的C/C++函数必须由调用者清除参数堆栈,下面是一个调用C/C++函数例子:
charszFormat[]="
%s%s\n"
charszHello[]="
Hello"
charszWorld[]="
world"
voidmain()
MOVEAX,OFFSETszWorld
PUSHEAX
MOVEAX,OFFSETszHello
MOVEAX,OFFSETszFormat
CALLprintf
//压入了3个参数在堆栈中,调用函数之后要调整堆栈
ADDESP,12
参数是按从右往左的顺序压入堆栈的。
如果调用__stdcall方式的函数,则不需要自己清除堆栈。
因为这种函数的返回指令是RETn,会自动清除堆栈。
大多数WindowsAPI函数均为__stdcall调用方式(仅除wsprintf等几个之外),下面是一个调用MessageBox函数的例子:
TCHARg_tszAppName[]=TEXT("
APITest"
);
TCHARtszHello[]=TEXT("
Hello,world!
PUSHMB_OKORMB_ICONINFORMATION
PUSHOFFSETg_tszAppName;
全局变量用OFFSET
LEAEAX,tszHello;
局部变量用LEA
PUSH0
CALLDWORDPTR[MessageBox];
注意这里不是CALLMessageBox,而是调用重定位过的函数地址
可以不受限制地访问C++成员变量,但是不能访问C++的成员函数。
7.定义__asm块为C/C++宏
使用C/C++宏可以方便地把汇编代码插入到源代码中。
但是这其中需要额外地注意,因为宏将会扩展到一个逻辑行中。
为了不会出现问题,请按以下规则编写宏:
使用花括号把__asm块包围住;
把__asm关键字放在每条汇编指令之前;
使用经典C风格的注释("
/*comment*/"
),不要使用汇编风格的注释("
comment"
)或单行的C/C++注释("
//comment"
);
举个例子,下面定义了一个简单的宏:
#definePORTIO__asm\
/*Portoutput*/\
{\
__asmMOVAL,2\
__asmMOVDX,0xD007\
__asmOUTDX,AL\
乍一看来,后面的三个__asm关键字好像是多余的。
其实它们是需要的,因为宏将被扩展到一个单行中:
__asm/*Portoutput*/{__asmMOVAL,2__asmMOVDX,0xD007__asmOUTDX,AL}
从扩展后的代码中可以看出,第三个和第四个__asm关键字是必须的(作为语句分隔符)。
在__asm块中,只有__asm关键字和换行符会被认为是语句分隔符,又因为定义为宏的一个语句块会被认为是一个逻辑行,所以必须在每条指令之前使用__asm关键字。
括号也是需要的,如果省略了它,编译器将不知道汇编代码在哪里结束,__asm块后面的C/C++语句看起来会被认为是汇编指令。
同样是由于宏展开的原因,汇编风格的注释("
)和单行的C/C++注释("
//commen"
)也可能会出现错误。
为了避免这些错误,在定义__asm块为宏时请使用经典C风格的注释("
)。
和C/C++宏一样__asm块写的宏也可以拥有参数。
和C/C++宏不一样的是,__asm宏不能返回一个值,因此,不能使用这种宏作为C/C++表达式。
不要不加选择地调用这种类型的宏。
比如,在声明为__fastcall的函数中调用汇编语言宏可能会导致不可预料的结果(请参看前文的说明)。
8.转跳
可以在C/C++里面使用goto转跳到__asm块中的标号处,也可以在__asm块中转跳到__asm块里面或外面的标号处。
__asm块内的标号是不区分大小写的(指令、指示符等也是不区分大小写的)。
voidMyFunction()
gotoC_Dest;
/*正确*/
gotoc_dest;
/*错误*/
gotoA_Dest;
gotoa_dest;
JMPC_Dest;
正确
JMPc_dest;
错误
JMPA_Dest;
JMPa_dest;
a_dest:
__asm标号
C_Dest:
/*C/C++标号*/
return;
不要使用函数名称当作标号,否则将转跳到函数中执行,而不是标号处。
例如,由于exit是C/C++的函数,下面的转跳将不会到exit标号处:
错误:
使用函数名作为标号
JNEexit...
exit:
......
美元符号"
$"
用于指定当前指令位置,常用于条件跳转中,例如:
JNE$+5;
下面这条指令的长度是5个字节
JMP_Label
NOP;
$+5,转跳到了这里
......
_Label:
.....
五、在VisualC++工程中使用独立汇编
内联汇编代码不易于移植,如果你的程序打算在不同类型的机器(比如x86和Alpha)上运行,你可能需要在不同的模块中使用特定的机器代码。
这时候你可以使用MASM(MicrosoftMacroAssembler),因为MASM支持更多方便的宏指令和数据指示符。
这里简单介绍一下在VisualStudio.NET2003中调用MASM编译独立汇编文件的步骤。
在VisualC++工程中,添加按MASM的要求编写的.asm文件。
在解决方案资源管理器中,右击这个文件,选择"
属性"
菜单项,在属性对话框中,点击"
自定义生成步骤"
,设置如下项目:
命令行:
ML.exe/nologo/c/coff"
-Fo$(IntDir)\$(InputName).obj"
"
$(InputPath)"
输出:
$(IntDir)\$(InputName).obj
如果要生成调试信息,可以在命令行中加入"
/Zi"
参数,还可以根据需要生成.lst和.sbr文件。
如果要在汇编文件中调用WindowsAPI,可以从网上下载MASM32包(包含了MASM汇编工具、非常完整的WindowsAPI头文件/库文件、实用宏以及大量的Win32汇编例子等)。
相应地,应该在命令行中加入"
/IX:
\MASM32\INCLUDE"
参数指定WindowsAPI汇编头文件(.inc)的路径。
MASM32的主页是:
,里面可以下载最新版本的MASM32包。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 嵌套 汇编语言