3

我需要在 Delphi 中编写 asm 函数来搜索最大数组元素。所以那是我写的。这里有几个问题。

首先 -mov ecx, len只是在这里以正确的方式工作。实际上它替换了 value inECX但不是 value in len!如果我只是写一个例子mov ecx, 5,那么 ecx 中就会出现 5。

其次 - 我在 5 个元素的数组上测试这个函数(使用mov ecx, 5ofc )它返回一些奇怪的结果。我想可能是因为我在尝试像这样读取数组 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.
4

1 回答 1

3

首先,调用约定:什么数据被发送到函数以及在哪里?

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

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

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


接下来,实际的功能逻辑。

  1. 您使用 16 位寄存器而不是完整的 32 位寄存器是否有原因?
  2. 您的数组包含字节,但您正在阅读和比较单词。
  3. lea ebx, dword ptr [edx]是一样的mov ebx, edx。您只是在引入另一个临时变量。
  4. 您正在比较元素,就好像它们已签名一样。
  5. 现代编译器倾向于在不使用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,您还需要将结果从 更改axeax

  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. 如果应用了值扩展,则需要将其从 更改movzxmovsx

  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
于 2012-11-17T15:38:18.490 回答