您当前的位置:首页 > 互联网教程

在c语言(C++或G++)中如何嵌入汇编

发布时间:2025-05-12 02:13:44    发布人:远客网络

在c语言(C++或G++)中如何嵌入汇编

一、在c语言(C++或G++)中如何嵌入汇编

1、上面的仁兄 mian()不是在吗,你没看到?

2、VC是可以嵌入汇编,可是你嵌入的是一个完整的汇编程序,这有些不合理吧.试想,如果可以嵌入完整的汇编程序,那 VC岂不是可以叫 VA(Visual Asm)了:)你把那些定义段的伪代码去掉,然后将变量定义放在 __asm{}前面(嵌入代码可以访问到这些变量的).然后再编译,应该没问题了:)

二、如何在C语言中嵌入汇编

使用内联汇编可以在 C/C++代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤。在 Visual C++中,内联汇编是内置的编译器,因此不需要配置诸如 MASM一类的独立汇编工具。这里,我们就以 Visual Studio.NET 2003为背景,介绍在 Visual C++中使用内联汇的相关知识(如果是早期的版本,可能会有些许出入)。

内联汇编代码可以使用 C/C++变量和函数,因此它能非常容易地整合到 C/C++代码中。它能做一些对于单独使用 C/C++来说非常笨重或不可能完成的任务。

使用内联汇编可以在 C/C++代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤。在 Visual C++中,内联汇编是内置的编译器,因此不需要配置诸如 MASM一类的独立汇编工具。这里,我们就以 Visual Studio.NET 2003为背景,介绍在 Visual C++中使用内联汇的相关知识(如果是早期的版本,可能会有些许出入)。

内联汇编代码可以使用 C/C++变量和函数,因此它能非常容易地整合到 C/C++代码中。它能做一些对于单独使用 C/C++来说非常笨重或不可能完成的任务。

编写对速度要求非常较高的代码;

在设备驱动程序中直接访问硬件;

编写 naked函数的初始化和结束代码。

使用内联汇编要用到 __asm关键字,它可以出现在任何允许 C/C++语句出现的地方。我们来看一些例子:

在每条汇编指令之前加 __asm关键字:

因为 __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关键字。

内联汇编支持 Intel Pentium 4和 AMD Athlon的所有指令。更多其它处理器的指令可以通过 _EMIT伪指令来创建(_EMIT伪指令说明见下文)。

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

虽然 __asm块中允许使用 C/C++的数据类型和对象,但它不能使用 MASM指示符和操作符来定义数据对象。这里特别指出,__asm块中不允许 MASM中的定义指示符(DB、DW、DD、DQ、DT和 DF),也不允许使用 DUP和 THIS操作符。MASM中的结构和记录也不再有效,内联汇编不接受 STRUC、RECORD、WIDTH或者 MASK。

尽管内联汇编不支持大多数 MASM指示符,但它支持 EVEN和 ALIGN。当需要的时候,这些指示符在汇编代码里面加入 NOP指令(空操作)使标号对齐到特定边界。这样可以使某些处理器取指令时具有更高的效率。

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

必须使用寄存器而不是名称来指明段(段名称"_TEXT"是无效的)。并且,段跨越必须显式地说明,如 ES:[EBX]。

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

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

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

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

例如,程序中定义了一个 8维的整数型变量:

下面是 C和汇编表达式中得到的 iArray及其元素的相关值:

LENGTH iArray sizeof(iArray)/sizeof(iArray[0]) 8

SIZE iArray sizeof(iArray) 32

TYPE iArray sizeof(iArray[0]) 4

内联汇编中可以使用汇编语言的注释,即";"。例如:

__asm MOV EAX, OFFSET pbBuff; Load address of pbBuff

因为 C/C++宏将会展开到一个逻辑行中,为了避免在宏中使用汇编语言注释带来的混乱,内联汇编也允许使用 C/C++风格的注释。

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

_EMIT 0x00;定义混合在代码段的数据

一般来说,不能假定某个寄存器在 __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++与汇编语言可以混合使用,在内联汇编中可以使用 C/C++变量以及很多其它的 C/C++元素,包括:

符号,包括标号、变量和函数名;

常量,包括符号常量和枚举型成员;

类型名,包括所有 MASM中合法的类型;

typedef名称,通常使用 PTR和 TYPE操作符,或者使用指定的的结构或枚举成员。

在内联汇编中,可以使用 C/C++或汇编语言的基数计数法。例如,0x100和 100H是相等的。

内联汇编中不能使用诸如"<<"一类的 C/C++操作符。但是,C/C++和 MASM共有的操作符(比如"*"和"[]"操作符),都被认为是汇编语言的操作符,是可以使用的。举个例子:

__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+ 12

iArray[6]= 0;// Store 0 at iArray+ 12

在 __asm块中可以引用所有在作用范围内的 C/C++符号,包括变量名称、函数名称和标号。但是不能访问 C++类的成员函数。

下面是在内联汇编中使用 C/C++符号的一些限制:

每条汇编语句只能包含一个 C/C++符号。在一条汇编指令中,多个符号只能出现在 LENGTH、TYPE或 SIZE表达式中。

在 __asm块中引用函数必须先声明。否则,编译器将不能区别 __asm块中的函数名和标号。

在 __asm块中不能使用对于 MASM来说是保留字的 C/C++符号(不区分大小写)。MASM保留字包含指令名称(如 PUSH)和寄存器名称(如 ESI)等。

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

内联汇编的一个非常大的方便之处是它可以使用名称来引用 C/C++变量。例如,如果 C/C++变量 iVar在作用范围内:

__asm MOV EAX, iVar; Stores the value of iVar in EAX

如果 C/C++中的类、结构或者枚举成员具有唯一的名称,则在 __asm块中可以只通过成员名称来访问(省略"."操作符之前的变量名或 typedef名称)。然而,如果成员不是唯一的,你必须在"."操作符之前加上变量名或 typedef名称。例如,下面的两个结构都具有 SameName这个成员变量:

那么,所有引用 SameName成员的地方都必须使用变量名,因为 SameName不是唯一的。另外,由于上面的 pszWeasel变量具有唯一的名称,你可以仅仅使用它的成员名称来引用它:

MOV ECX, [EBX]ftTest.SameName;必须使用"ftTest"

MOV ESI, [EBX]. pszWeasel;可以省略"ftTest"

提示:省略变量名仅仅是为了书写代码方便,生成的汇编指令还是一样的。

如果用内联汇编写函数的话,要传递参数和返回一个值都是非常容易的。看下面的例子,比较一下用独立汇编和内联汇编写的函数:

; Compute the power of an integer

_TEXT SEGMENT WORD PUBLIC'CODE'

MOV EBP, ESP; Move ESP into EBP so we can refer

MOV EAX, [EBP+4]; Get first argument

MOV ECX, [EBP+6]; Get second argument

SHL EAX, CL; EAX= EAX*(2 ^ CL)

C/C++函数一般用堆栈来传递参数,所以上面的函数中需要通过堆栈位置来访问它的参数(在 MASM或其它一些汇编工具中,也允许通过名称来访问堆栈参数和局部堆栈变量)。

下面的程序是使用内联汇编写的:

int GetPowerC(int iNum, int iPower);

printf("3 times 2 to the power of 5 is%d\n", GetPowerC( 3, 5));

int GetPowerC(int iNum, int iPower)

MOV EAX, iNum; Get first argument

MOV ECX, iPower; Get second argument

SHL EAX, CL; EAX= EAX*(2 to the power of CL)

使用内联汇编写的 GetPowerC函数可以通过参数名称来引用它的参数。由于 GetPowerC函数没有执行 C的 return语句,所以编译器会给出一个警告信息,我们可以通过#pragma warning禁止生成这个警告。

内联汇编的其中一个用途是编写 naked函数的初始化和结束代码。对于一般的函数,编译器会自动帮我们生成函数的初始化(构建参数指针和分配局部变量等)和结束代码(平衡堆栈和返回一个值等)。使用内联汇编,我们可以自己编写干干净净的函数。当然,此时我们必须自己动手做一些有关函数初始化和扫尾的工作。例如:

void __declspec(naked) MyNakedFunction()

// Naked functions must provide their own prolog.

// And we must provide epilog.

内联汇编中调用声明为 __cdecl方式(默认)的 C/C++函数必须由调用者清除参数堆栈,下面是一个调用 C/C++函数例子:

char szFormat[]="%s%s\n";

//压入了 3个参数在堆栈中,调用函数之后要调整堆栈

提示:参数是按从右往左的顺序压入堆栈的。

如果调用 __stdcall方式的函数,则不需要自己清除堆栈。因为这种函数的返回指令是 RET n,会自动清除堆栈。大多数 Windows API函数均为 __stdcall调用方式(仅除 wsprintf等几个之外),下面是一个调用 MessageBox函数的例子:

TCHAR g_tszAppName[]= TEXT("API Test");

TCHAR tszHello[]= TEXT("Hello, world!");

PUSH MB_OK OR MB_ICONINFORMATION

PUSH OFFSET g_tszAppName;全局变量用 OFFSET

LEA EAX, tszHello;局部变量用 LEA

CALL DWORD PTR [MessageBox];注意这里不是 CALL MessageBox,而是调用重定位过的函数地址

提示:可以不受限制地访问 C++成员变量,但是不能访问 C++的成员函数。

使用 C/C++宏可以方便地把汇编代码插入到源代码中。但是这其中需要额外地注意,因为宏将会扩展到一个逻辑行中。

为了不会出现问题,请按以下规则编写宏:

把 __asm关键字放在每条汇编指令之前;

使用经典 C风格的注释("/* comment*/"),不要使用汇编风格的注释("; comment")或单行的 C/C++注释("// comment");

举个例子,下面定义了一个简单的宏:

乍一看来,后面的三个 __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的函数中调用汇编语言宏可能会导致不可预料的结果(请参看前文的说明)。

可以在 C/C++里面使用 goto转跳到 __asm块中的标号处,也可以在 __asm块中转跳到 __asm块里面或外面的标号处。__asm块内的标号是不区分大小写的(指令、指示符等也是不区分大小写的)。例如:

不要使用函数名称当作标号,否则将转跳到函数中执行,而不是标号处。例如,由于 exit是 C/C++的函数,下面的转跳将不会到 exit标号处:

美元符号"$"用于指定当前指令位置,常用于条件跳转中,例如:

JNE$+5;下面这条指令的长度是 5个字节

五、在 Visual C++工程中使用独立汇编

内联汇编代码不易于移植,如果你的程序打算在不同类型的机器(比如 x86和 Alpha)上运行,你可能需要在不同的模块中使用特定的机器代码。这时候你可以使用 MASM(Microsoft Macro Assembler),因为 MASM支持更多方便的宏指令和数据指示符。

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

在 Visual C++工程中,添加按 MASM的要求编写的.asm文件。在解决方案资源管理器中,右击这个文件,选择"属性"菜单项,在属性对话框中,点击"自定义生成步骤",设置如下项目:

命令行:ML.exe/nologo/c/coff"-Fo$(IntDir)\$(InputName).obj""$(InputPath)"

输出:$(IntDir)\$(InputName).obj

如果要生成调试信息,可以在命令行中加入"/Zi"参数,还可以根据需要生成.lst和.sbr文件。

如果要在汇编文件中调用 Windows API,可以从网上下载 MASM32包(包含了 MASM汇编工具、非常完整的 Windows API头文件/库文件、实用宏以及大量的 Win32汇编例子等)。相应地,应该在命令行中加入"/I X:\MASM32\INCLUDE"参数指定 Windows API汇编头文件(.inc)的路径。MASM32的主页是:,里面可以下载最新版本的 MASM32包。

三、在C51语言中如何嵌入汇编语言

keil C语言中嵌入汇编语言进行混合编程,方法如下:

1、在C文件中要嵌入汇编代码片以如下方式加入汇编代码

2、在Project窗口中包含汇编代码的C文件上右键,选择“Options for...”

“Generate Assembler SRC File”

使检查框由灰色变成黑色(有效)状态

3、根据选择的编译模式,把相应的库文件(如Small模式时,是

Keil\C51\Lib\C51S.Lib)加入工程中,该文件必须作为工程的最后文件,在默认