将引用参数传递给程序集函数



我有一个带有 3 个引用参数的函数,其中包含一些汇编代码。 我想在变量 R、G、B 中得到该函数的结果,如下所示。

void Get_RGB_color(const DWORD &color, uint8_t &R, uint8_t & G, uint8_t & B)     {
_asm {
push EAX;
xor EAX, EAX;
mov EAX,color;
mov R, AL;
mov G, AH;
shr EAX, 16;
mov B, AL;
pop EAX;
}
}

例如,我将函数用作

DWORD  color=RGB(76,0,0);
uint8_t R,   G, B;
Get_RGB_color(color , R ,G ,B );

代码有两个问题:

1-在EAX中获取错误的值,在行中移动EAX,颜色;

2-行中的错误"操作数大小冲突"

莫夫· 莫夫· 莫夫 B,阿拉巴马州;

请帮助我

push EAX;

你为什么要在这里推动EAX?没有必要这样做。EAX 是一个调用者保存寄存器,这意味着被调用方(您的函数)可以自由地破坏它。您不需要保留其价值。
(EAX、EDX 和 ECX 是 Win32 ABI 中的调用方保存寄存器;其他寄存器是被调用方保存寄存器。

推送寄存器的唯一其他原因是对齐堆栈,但这在这里也不是必需的。当控制权传递给函数时,堆栈已经正确对齐。

xor EAX, EAX;

我想你知道这是清除寄存器(与自身进行 XOR )的常用技巧。但是,在将值MOVe插入寄存器之前,无需预先清除寄存器。

mov EAX,color;

这一行是错误的;这就是汇编程序错误告诉你的。color作为对 DWORD 的引用传递给此函数,但在后台,引用作为指针实现,因此它实际上是作为指向 DWORD 的指针传递的。这意味着您无法直接访问颜色的值 - 您必须使用指针间接寻址(或 x86 用语中的"间接寻址")。由于您使用的是内联程序集,因此您可以让编译器为您进行堆栈簿记,只需通过形式参数的名称引用内存位置:

mov EAX, DWORD PTR [color]   ; get the address of color
mov EAX, DWORD PTR [EAX]     ; dereference it, storing the result in EAX

当然,由于您实际上并没有在此函数内部修改color,因此没有理由将其作为引用参数传递。通常,标量值(例如整数)应始终按值传递,而不是按引用传递,除非您实际需要引用。这既高效又可读 - 优化编译器将在寄存器中传递值,使此指针间接及其附带的成本完全没有必要。

mov R, AL;

在这里,汇编程序会给您一个"操作数大小冲突"错误。因为R实际上是一个引用,作为一个指针实现,所以它是 32 位的。它是一个 32 位指针,指向内存中的 8 位位置。因此,您正在尝试将 8 位值 (AL) 移动到 32 位位置(指针)。操作数的大小不同。因此,您必须再次使用间接寻址。它看起来就像上面的代码一样,只是现在R是字节大小的,您需要使用不同的寄存器作为暂存寄存器,以避免破坏我们努力实现的 EAX 中的值:

mov EDX, DWORD PTR [R]    ; get the address of R
mov BYTE PTR [EDX], AL    ; dereference it so we can store AL in there

这会将 EAX 的低字节(我们可以称为 AL)移动到R指定的字节大小的内存位置。

下一行也是如此,只是现在您正在移动 EAX 的高字节(称为 AH)。我们现在可以在这里重用 EDX,因为我们不再需要它的旧值:

mov EDX, DWORD PTR [G]    ; get the address of G
mov BYTE PTR [EDX], AH    ; dereference it so we can store AH in there
shr EAX, 16;

这是正确的。

mov B, AL;

第三节,与第一节相同。如您现在所知,这应该是:

mov EDX, DWORD PTR [B]    ; get the address of B
mov BYTE PTR [EDX], AL    ; dereference it so we can store AL in there
pop EAX;

现在不需要弹出 EAX,因为我们一开始没有推送 EAX。

将它们

放在一起,然后,您将获得以下指令序列:

void Get_RGB_color(const DWORD &color, uint8_t &R, uint8_t & G, uint8_t & B) 
{
__asm
{
mov  EAX, DWORD PTR [color]
mov  EAX, DWORD PTR [EAX]
mov  EDX, DWORD PTR [R]
mov  BYTE PTR [EDX], AL
mov  EDX, DWORD PTR [G]
mov  BYTE PTR [EDX], AH
shr  EAX, 16
mov  EDX, DWORD PTR [B]
mov  BYTE PTR [EDX], AL
}
}

但是,这不是编写代码的最佳方法。访问 32 位寄存器的低 8 位和高 8 位虽然允许,但速度很慢。优化编译器将避免这种情况,并在此过程中避免需要移位指令:

void Get_RGB_color(const DWORD &color, uint8_t &R, uint8_t & G, uint8_t & B) 
{
__asm
{
mov  EAX, DWORD PTR [color]   ; get the address of color
mov  EAX, DWORD PTR [EAX]     ; get the value in EAX
mov  EDX, DWORD PTR [R]       ; get the address of R
mov  CL,  BYTE PTR [EAX]      ; get the value of the lowest byte (8 bits) of color
mov  BYTE PTR [EDX], CL       ; dereference R and store that byte in it
mov  EDX, DWORD PTR [G]       ; get the address of G
mov  CL, BYTE PTR [EAX + 1]   ; get the value of the second-to-lowest byte in color
mov  BYTE PTR [EDX], CL       ; dereference G and store that byte in it
mov  EDX, DWORD PTR [B]       ; get the address of B
mov  CL, BYTE PTR [EAX + 2]   ; get the value of the third-to-lowest byte in color
mov  BYTE PTR [EDX], CL       ; dereference B and store that byte in it
}
}

但仍然有部分收银摊位潜伏在那里,以减缓事情的发展。因此,一个真正聪明的编译器会通过预置零寄存器或使用movzx来消除这些:

void Get_RGB_color(const DWORD &color, uint8_t &R, uint8_t & G, uint8_t & B) 
{
__asm
{
mov    EAX, DWORD PTR [color]
mov    EAX, DWORD PTR [EAX]
mov    EDX, DWORD PTR [R]
movzx  ECX, BYTE PTR [EAX]
mov    BYTE PTR [EDX], CL
mov    EDX, DWORD PTR [G]
movzx  ECX, BYTE PTR [EAX + 1]
mov    BYTE PTR [EDX], CL
mov    EDX, DWORD PTR [B]
movzx  ECX, BYTE PTR [EAX + 2]
mov    BYTE PTR [EDX], CL
}
}

它还可能重新排序指令并巧妙地分配寄存器以尽可能并行化三个操作。毫无疑问,还有更有效的方法可以做到这一点。除非你正在尝试学习汇编语言编程(在这种情况下,使用内联汇编程序没有多大意义),否则强烈喜欢像这样编写代码

void Get_RGB_color(const DWORD &color, uint8_t &R, uint8_t & G, uint8_t & B) 
{
R = (color & 0xFF);
G = ((color >>  8) & 0xFF);
B = ((color >> 16) & 0xFF);
}

最后一点:您不需要以分号结束每个汇编语言指令,即使在使用内联汇编语法时也是如此。这仅在 C 和 C++ 中是必需的。不过,这并不重要的原因,因为分号实际上是汇编程序中的注释分隔符,所以它就像用 C 编写以下内容一样:

int foo;/**/

最新更新