如果你想编写出极高性能的游戏代码,那么使用汇编语言无疑将会是你的最佳选择。不过,眼看着编程技术已经发展到了今天这种格局,你再去直接用汇编语言来进行编程也未免太不合时宜了吧。作为一个高性能游戏程序的编写者,你应该用的是 Inline Assembly。
什么是 Inline Assembly
Inline Assembly 就是我们通常所说的在线汇编,即直接在你的 C/C++ 代码里加入汇编语言代码。
在线汇编的好处
那些习惯了在线汇编的人都觉得它用起来很方便。
同传统的汇编方式相比,我们可以完全避免那些烦琐的汇编和链接步骤,可以直接在汇编代码中使用 C 变量名和函数名。这样一来,我们的汇编代码就能够很容易地同我们的 C 语言程序紧密而自然地结合在一起了。另外,由于是把汇编语句和 C 或者 C++ 语句混合在一起进行编程,我们还将能实现许多原来光凭 C 或者 C++ 语句无法办到的事。
在线汇编的用处
通常,在线汇编被用来写某些特殊函数,例如优化代码中对速度有极高要求的部分,直接访问硬件和设备驱动程序和为一段调用代码编写进入和退出时用到的保护性代码等等。
在线汇编的限制
一方面,在线汇编是一种专用工具,它不支持宏汇编中的宏和其它一些功能。另一方面,当你想往不同的平台移植程序代码时,这些机器相关的代码会比较难于处理。
如何在 Visual C++ 环境中使用在线汇编
在 Visual C++ 环境下,使用在线汇编是一件非常容易的事情:你只要用一条“_asm { }”把你的汇编代码括起来就行了。
下面给大家看一个简单例子,这个例子是要实现一个叫 Mycode() 的 C 函数。这个函数的功能很简单,它把两个输入参数的值相加,结果存放到另外一个全局变量中。
long result;
void Mycode(long a, long *b)
{
_asm
{
MOV EAX, a // eax = a
MOV EBX, b // ebx = b
ADD EAX, [EBX] // eax = eax + [ebx] = eax + (*b)
MOV result, EAX // result = eax
}
}
这个例子虽然简单,但它在解释“_asm { }”的用法的同时,顺便演示了对一般变量和指针变量的不同处理方法。
Visual C++ Inline Assembly 的处理细节
根据微软的不完全资料和自己的长期实践,我总结出以下几条处理细节,供大家参考,也期待着大家来补充和修正。
在 32 位应用程序中,因为是处于保护模式,所有寻址的偏移地址都是 32 位的,此时应该尽量避免修改和使用段寄存器。
全局变量被翻译成立即地址。
函数内的局部变量被翻译成堆栈偏移地址,形如 [EBP + ????] 的形式,其中 EBP 在函数进入时被设定为 ESP 的值。故使用局部变量的函数中,注意不要破坏 EBP 的内容。
Visual C++ 编译器不会对位于“_asm { }”中的代码作任何优化和改动。
Visual C++ 编译器在优化时,会自动废弃穿越“_asm { }”代码段的寄存器,因此不需要在“_asm { }”代码段中添加寄存器保护和恢复代码。 补充说明
匆忙之中,就想到了这么几条,如果你有什么意见、问题和建议,请务必提出来,那样的话,大家可以一起作进一步的讨论,使本文得以完善和升级,从而让更多看到本文的人受益。
另外,如果你以前有一点汇编语言的基础,但没学过 INTEL X86 体系的 32 位汇编语言,请阅读我的另一篇文字:『INTEL X86 体系的 32 位汇编语言速成』。
VC 里如果你定义了结构, 比如:typedef struct {
int x;
int y;
} MYSTRUCT;
那么在 inline asm 里就多了两个常量:MYSTRUCT.x 是 0
MYSTURCT.y 是 4
它们表示了 x,y 在结构中的偏移量。所以你就可以用:MYSTURCT *a;
mov ebx,a;
mov eax,[ebx]MYSTRUCT.x;
(eax 里就是 a->x)
上面一行也可以写成 mov eax,[ebx+MYSTRUCT.x];
另外,VC 中你也可以创建一个函数不帮你做栈处理的,需要你自己来取输入参数、保护寄存器、甚至自己写 ret 等等。这可以看做一个“纯粹”的 asm 函数了。方法是在函数定义前加入: __declspec(naked)
就可以了。
VC inline asm 里可以用 ALIGN n 来对齐代码。如果需要填入数据,VC 将填入一些不改变任何寄存器和标志位的指令(不一定是 nop),不过小心 VC 在这方面有 bug,我的主页(cloudwu.yeah.net)上曾详细讲解。
另外,在 VC 的 inline asm 里不象我们在 BC 的 inline asm 里那样简洁的用 db 来插入一些非代码的数据,它要用 _emit n 插入一个字节。这有时很有用,比如 Pentium 的 cpuid 指令 VC 就不支持,我们可以用: #define cpuid __asm _emit 0x0F __asm _emit 0xA2
来代替就可以了。