asm指针,我错了什么?

时间:2015-07-09 19:25:04

标签: pointers assembly

int sort(int* list)
{
    __asm
    {
        mov esi, [list];
            mov eax, dword ptr[esi + edx * 4];   store pointer to eax?
            mov edi, dword ptr[esi + 4 + edx * 4]; store pointer to edi?
            jmp swap;

swap:
        push dword ptr[esi + edx * 4];
        mov dword ptr[esi + edx * 4], edi;      
        pop dword ptr[esi + 4 + edx * 4];

这是我的家庭作业代码的一部分,它工作正常但我想知道如何更改我的交换使用寄存器而不是dword ptrs。我最初有:

swap: (none of this works... values remain unchanged. why? =[ )
            push eax; supposed to push value pointed to?
            mov eax, edi; supposed to change value pointed at by eax? 
            pop edi; supposed to pop pushed value into edi pointer location?

但实际上并没有交换任何东西,传入的数组并没有改变。我怎样才能重写我的代码,以便swap语句看起来像这样?我尝试在上面的交换语句中将[]放在eax中,但这也不起作用。

3 个答案:

答案 0 :(得分:0)

有三条指令(如Kerrek SB所说)和只有一个寄存器(EAX):

int exchange ()
{ int list[5] = {1,5,2,4,3};
  __asm { mov edx, 0
          lea esi, list
          // SWAP WITH THREE INSTRUCTIONS.
            mov  eax, [esi + edx * 4]
            xchg [esi + 4 + edx * 4], eax
            mov  [esi + edx * 4], eax
            // NOW LIST =  {5,1,2,4,3};
        }
}

或者,将数组作为参数:

int exchange ( int * list )
{  __asm { mov edx, 0
           mov esi, list
           // SWAP WITH THREE INSTRUCTIONS.
           mov  eax, [esi + edx * 4]
           xchg [esi + 4 + edx * 4], eax
           mov  [esi + edx * 4], eax
           // LIST =  {5,1,2,4,3};
         }
}

这就是如何称呼它:

int list[5] = {1,5,2,4,3};
exchange( list );

答案 1 :(得分:0)

部分混淆可能是您的功能如何接收其输入。如果您在asm中编写整个函数,而不是使用特定于MSVC的语法内联,那么the ABI会告诉您参数将在堆栈中(对于32位x86代码)。 http://www.agner.org/optimize/也有一个调用约定文档,涵盖了x86和x86-64的各种不同的调用约定。

反正。

xchg可能看起来就像您想要进行交换的指令。如果您确实需要交换两个寄存器的内容,则它在性能上与3 mov指令非常相似,否则将需要这些指令,但不需要临时寄存器。但是,实际上需要交换两个寄存器,而不是仅覆盖一个寄存器,或者将旧值保存在其他地方,这种情况有点罕见。此外,Ivy Bridge / Haswell的3 mov reg, reg会更快,因为它们不需要执行单元;他们只是在寄存器重命名阶段处理它(延迟为0)。

对于交换两个内存位置的内容,它比使用mov加载/存储的速度慢至少25倍,因为隐式LOCK前缀强制CPU确保所有其他内核立即获得更新,而不是仅写入L1缓存。

您需要做的是2次装载和2次存储。

最简单的形式(2个装载,2个商店,在一般情况下工作)将是

# void swap34(int *array)

swap34:
 # 32bit: mov edi, [esp+4]   # [esp] has the return address
 # 64bit windows: mov rdi, rcx  # first function arg comes in rcx
# array pointer in rdi, assuming 64bit SystemV (Linux) ABI.
mov eax, [rdi+8]    # load array[3]
mov ebx, [rdi+12]   # load array[4]
mov [rdi+12], eax   # store array[4] = tmp1
mov [rdi+8],  ebx   # store array[3] = tmp2
ret

使用更复杂的寻址模式(例如[rdi+rax*4]),您可以将list[rax]list[rbx]交换。)

如果内存位置相邻,您可以使用更宽的负载同时加载两者,并旋转以交换。 e.g。

# int *array in rdi
mov  rax, [rdi+4]  # load 2nd and 3rd 32bit element
rol  rax, 32       # rotate left by half the reg width
mov  [rdi+4], rax  # store back to the same place

我相信这3条指令的运行速度实际上比rol [rdi+4], 32快。 (在内存操作数上旋转,英特尔Sandybridge上的imm8计数为4微秒,吞吐量为每2个周期1个。加载/腐烂/存储为3微秒,每个周期应维持1个。内存操作数版本使用更少但是,它不会将任何一个值保留在寄存器中。通常在实际代码中,您可能希望使用其中一个值继续执行某些操作。)

如果您有rsirdi指向要交换的值,我可以考虑使用更少指令的唯一方法。那么你可以

movd  eax, [rdi]  ;  DON'T DO THIS,
movsd    ; string-move, 4B version.  copies [rsi] to [rdi]
movd  [rsi-4], eax ;  IT'S SLOW

这比2次加载/ 2次存储慢很多,movsd递增rsirdi。在这里保存指令实际上会导致代码速度变慢,而代码在最近的英特尔设计中使用uop缓存中的更多空间。 (没有movsd前缀的rep永远不是一个好选择。)

从一个内存位置读取并写入另一个内存位置的另一个指令是带有内存操作数的poppush,但只有在堆栈指针指向您想要的值之一时才有效交换,你并不关心更改堆栈指针。

不要弄乱堆栈指针。从理论上讲,您可以将堆栈指针保存在某处,并将其用作另一个GP寄存器,用于您不在寄存器中的循环,但前提是您不需要调用任何内容,并且不会发生任何异步操作当rsp没有指向堆栈时,可能会尝试使用堆栈。说真的,即使手写的性能调整的asm使用堆栈指针除了正常使用之外的任何东西真的很少见,所以真的忘了我提到它。

答案 2 :(得分:0)

据我所知,您希望在两个不同的数组中交换两个双字值,并且您希望使用两个寄存器来完成此操作。您正在加载EAXEDI两个值(每个数组一个),在您交换寄存器值之后,需要将它们存储/保存回内存中的相应数组偏移量,以便更改它们的值。所以继续你的代码行,试试:

Push Eax
Mov  Eax, Edi
Pop  Edi
Mov  dword ptr[esi + 4 + edx * 4], Eax
Mov  dword ptr[esi + edx * 4], Edi

当目标操作数是扩展寄存器时,您可以省略dword ptr类型覆盖前缀,我相信将假设源值将是相同的大小(双字)。所以这也有效:

mov eax, [esi + edx * 4]  
mov edi, [esi + 4 + edx * 4]

此外,您是否必须使用该寻址方式?您似乎正在使用间接索引位移寻址。