2
votes

I have a function with 3 reference arguments that contains some assembly codes. I want to get the result of that function in variables R , G , B as follow.

     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;
    }
  }

for example i used the function as

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

The codes have two problems:

1- Getting wrong value in EAX , in line mov EAX,color;

2- Error 'operand size conflict' in lines

mov R, AL; mov G, AH; mov B, AL;

Please help me

1
References are actually passed as pointers, you need to treat them as such. PS: there is no reason to use assembly for this.Jester
I think i get the address of variables,How can i get the value of the variables?SaeidMo7
I'd reinforce what Jester already said. Do you think this assembly will be faster than compiler generated code?Adriano Repetti
I think that assembly code is shorter way for getting results, speed is not important for that subject.SaeidMo7
Writing the code in assembly will be neither shorter nor faster in this case. The compiler can do a much better job with simple code like this. Speaking of faster and shorter, you would be much better off passing simple scalar types like color by value, rather than by reference. That way, the value can simply be passed in a register.Cody Gray

1 Answers

8
votes
push EAX;

Why are you pushing EAX here? There is no need to do that. EAX is a caller-save register, which means that the callee (i.e., your function) is free to clobber it. You aren't required to preserve its value.
(EAX, EDX, and ECX are the caller-save registers in the Win32 ABI; the others are callee-save.)

The only other reason to push a register would be to align the stack, but that is not necessary here, either. The stack will already be aligned correctly when control passes to your function.

xor EAX, EAX;

I presume you know that this is a commonly-used trick for clearing a register (XORing it with itself). However, you do not need to pre-clear a register before you MOVe a value into it.

mov EAX,color;

This line is wrong; that is what the assembler error is telling you. color is passed to this function as a reference-to-DWORD, but under the hood, references are implemented as pointers, so it is actually passed as a pointer-to-DWORD. Which means that you cannot access the value of color directly—you must use pointer indirection (or "indirect addressing" in x86 parlance). Since you are using inline assembly, you can let the compiler do the stack bookkeeping for you and just refer to the memory location by the name of the formal parameter:

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

Of course, since you are not actually modifying color inside of this function, there is no reason to pass it as a reference parameter. In general, scalar values (e.g., integers) should always be passed by value, rather than by reference, unless you actually need the reference. This is both more efficient and more readable—an optimizing compiler will pass the value in a register, making this pointer indirection and its attendant cost completely unnecessary.

mov R, AL;

Here, the assembler is giving you an "operand size conflict" error. Because R is actually a reference, implemented as a pointer, it is 32-bits. It is a 32-bit pointer that points to an 8-bit location in memory. So you are trying to move an 8-bit value (AL) into a 32-bit location (the pointer). The operands are different sizes. So once again, you have to use indirect addressing. It looks just like the code above, except that now R is byte-sized and you need to use a different register as a scratch register in order to avoid clobbering the value in EAX we worked so hard to get there:

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

This moves the low byte of EAX (which we can refer to as AL) into the byte-sized memory location designated by R.

Same thing for the next line, except that now you're moving the high byte of EAX (referred to as AH). We can reuse EDX now here, because we don't need its old value again:

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;

This is correct.

mov B, AL;

Third verse, same as the first. As you now know, this should be:

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;

Popping EAX is now unnecessary, since we didn't push EAX at the beginning.


Putting it all together, then, you get the following sequence of instructions:

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
    }
}

However, this is not the most optimal way to write the code. Accessing the low and high 8 bits of a 32-bit register, while permissible, is slow. An optimizing compiler would avoid this, and in the process, avoid the need for a shift instruction:

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
    }
}

But there are still partial register stalls lurking in there to slow things down. So a really smart compiler would eliminate those by either pre-zeroing the registers or using 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
    }
}

It might also reorder the instructions and cleverly allocate registers to parallelize the three operations as much as possible. There are doubtless even more efficient ways of doing this. Unless you're trying to learn assembly language programming (in which case, using the inline assembler doesn't make much sense), strongly prefer to write the code like this:

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);
}

One final note: you do not need to end each assembly language instruction with a semicolon—even when using the inline assembly syntax. That's only necessary in C and C++. The reason why it doesn't matter, though, because a semicolon is actually a comment delimiter in assembler, so it would be like writing the following in C:

int foo;/**/