汇编数组最大元素搜索

3
我需要在Delphi中编写汇编函数来查找最大的数组元素。以下是我的代码。但是出现了一些问题。
第一个问题是,mov ecx, len 在这里并没有按照正确的方式工作。实际上它只是替换了ECX中的值,而不是用len的值来替换。如果我写一个例子mov ecx, 5,那么ecx中就会出现5。
第二个问题是,我用包含5个元素的数组测试了这个函数(当然使用了mov ecx, 5),它返回了一些奇怪的结果。我认为这可能是因为我在尝试读取数组的0号元素时做了一些错误的操作,如下所示:
mov edx, arr
      lea ebx, dword ptr [edx]

但如果我这样阅读它
  lea ebx, arr

操作无效,并且如果我这样尝试,它会提示。
lea bx, arr

提示尺寸不匹配。

我该如何解决这个问题?完整代码如下:

 program Project2;

    {$APPTYPE CONSOLE}

    uses
      SysUtils;

    Type
      TMyArray = Array [0..255] Of Byte;



    function randArrCreate(len:Integer):TMyArray;
    var temp:TMyArray; i:Integer;
    begin
      Randomize;
      for i:=0 to len-1 do
        temp[i]:=Random(100);
      Result:= temp;
    end;

    procedure arrLoop(arr:TMyArray; len:Integer);
    var i:integer;
    begin
      for i:=0 to len-1 do begin
          Write(' ');
          Write(arr[i]);
          Write(' ');
        end;
    end;

    function arrMaxAsm(arr:TMyArray; len:integer):Word; assembler;
    asm
      mov edx, arr
      lea ebx, dword ptr [edx]
      mov ecx, len
      xor ax,ax  //0
      mov ax, [ebx]  //max

      @cycle:
        mov dx, [ebx]
        cmp dx, ax
        jg @change
        jmp @cont
      @change:
        mov ax, dx
      @cont:
        inc ebx
      loop @cycle

      mov result, ax
    end;


    var massive:TMyArray; n,res:Integer;
    begin
      Readln(n);
      massive:=randArrCreate(n);//just create random array
      arrLoop(massive,n);//just to show what in it
      res:=arrMaxAsm(massive, n);
      Writeln(res);
      Readln(n);
    end.
1个回答

3

首先,调用约定:哪些数据被发送到函数中以及它们的位置?

根据文档,数组作为指向数据的32位指针传递,整数作为值传递。

同一份文档指出,支持多种调用约定。不幸的是,默认约定没有记录 - 明确指定一个是个好主意。

根据您的描述,mov ecx, len无法工作,我猜编译器默认使用了register约定,并且参数已经放置在ecxedx中,然后您的代码把它们混淆了。您可以更改您的代码以适应该约定,或者告诉编译器使用堆栈传递参数 - 使用stdcall约定。我随意选择了第二个选项。无论您选择哪个选项,请务必明确指定调用约定。


接下来,实际的函数逻辑。

  1. 你使用16位寄存器而不是32位寄存器有什么原因吗?
  2. 你的数组包含字节,但是你正在读取和比较字。
  3. lea ebx, dword ptr [edx]mov ebx, edx 是一样的。你只是引入了另一个临时变量。
  4. 你正在将元素作为有符号数进行比较。
  5. 现代编译器 tend to implement loops without using loop
  6. 文档还说需要保留 ebx - 因为该函数使用了 ebx,所以需要在开始时保存其原始值并在结束时恢复。

我用Lazarus重写了你的函数(因为我已经8年没碰Delphi了——没有编译器可用):

function arrMaxAsm(arr:TMyArray; len:integer):Word; assembler; stdcall;
 asm
  push ebx                 { save ebx }

  lea edx, arr             { Lazarus accepts a simple "mov edx, arr" }
  mov edx, [edx]           { but Delphi 7 requires this indirection }

  mov ecx, len
  xor ax, ax               { set default max to 0 }
  test ecx, ecx
  jle @done                { if len is <= 0, nothing to do }
  movzx ax, byte ptr [edx] { read a byte, zero-extend it to a word } 
                           { and set it as current max }

 @cont:
  dec ecx                  
  jz @done                 { if no elements left, return current max }

 @cycle:
  inc edx
  movzx bx, byte ptr [edx] { read next element, zero-extend it }
  cmp bx, ax               { compare against current max as unsigned quantities }
  jbe @cont
  mov ax, bx
  jmp @cont

 @done:
  pop ebx                  { restore saved ebx }
  mov result, ax
end;

通过重新组织循环跳转,可能可以进一步优化它- YMMV。


注意:这仅适用于字节大小的无符号值。要使其适应不同大小/有符号性质的值,需要进行一些更改:

数据大小:

  1. 读取正确数量的字节:
    movzx bx, byte ptr [edx]  { byte-sized values }

    mov bx, word ptr [edx]    { word-sized values }

    mov ebx, dword ptr [edx]  { dword-sized values }
                              { note that the full ebx is needed to store this value... }

请注意,这个读取过程在两个地方进行。如果你正在处理dwords,你还需要将结果从ax更改为eax
1. 在正确的字节数上进行高级操作。
     @cycle:
      inc edx    { for an array of bytes }

      add edx, 2 { for an array of words }

      add edx, 4 { for an array of dwords }

处理有符号值:

  1. 如果应用了值扩展,则需要将其从 movzx 更改为 movsx

  2. 在设置新的最大值之前,需要调整条件跳转:

      cmp bx, ax         { compare against current max as unsigned quantities }
      jbe @cont

      cmp bx, ax         { compare against current max as signed quantities }
      jle @cont

1
@DanilGholtsman:恐怕我无法解释那部分 - Lazarus可以编译它。我所提到的文档可能不适用于您正在使用的Delphi版本。 - DCoder
1
@DanilGholtsman:我找到了一份D7的副本并成功编译了 - 请参见编辑后的帖子。 eaxecxedx寄存器被称为“scratch”寄存器,您的代码可以自由使用它们,但其他寄存器用于实现细节,编译器期望它们的值不会改变 - 由于该函数使用bx来存储临时值,因此必须包括此保存/恢复对。 - DCoder
1
你正在测试哪些具体的数据? - DCoder
哦,我忘了说我现在在整数上测试它,但对于字节来说它完美地工作。 - DanilGholtsman
1
@DanilGholtsman:我更新了答案,并添加了处理其他数据类型的注释。至于“阅读什么”,那就偏离了原始问题的范畴 :) 我是通过阅读英特尔手册并查看C/C++编译器的汇编输出,直到理解为止学习的,但我认为这个问题是一个很好的起点。 - DCoder
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接