5

我在 Delphi 7 中编写了一个 asm 函数,但它将我的代码转换为其他内容:

function f(x: Cardinal): Cardinal; register;
label err;
asm
  not eax
  mov edx,eax
  shr edx, 1
  and eax, edx
  bsf ecx, eax
  jz  err
  mov eax, 1
  shl eax, cl
  mov edx, eax
  add edx, edx
  or  eax, edx
  ret
  err:
  xor eax, eax
end;

// compiled version
f:
  push ebx       // !!!
  not eax
  mov edx,eax
  shr edx, 1
  and eax, edx
  bsf ecx, eax
  jz  +$0e
  mov eax, 1
  shl eax, cl
  mov edx, eax
  add edx, edx
  or  eax, edx
  ret
  err:
  xor eax, eax
  mov eax, ebx   // !!!
  pop ebx        // !!!
  ret

// the almost equivalent without asm
function f(x: Cardinal): Cardinal;
var
  c: Cardinal;
begin
  x := not x;
  x := x and x shr 1;
  if x <> 0 then
  begin
    c := bsf(x); // bitscanforward
    x := 1 shl c;
    Result := x or (x shl 1)
  end
  else
    Result := 0;
end;

为什么会生成push ebxand pop ebx?为什么会这样mov eax, ebx

似乎它生成了部分堆栈帧,因为mov eax, ebx.

这个简单的测试生成mov eax, edx但不生成该堆栈帧:

function asmtest(x: Cardinal): Cardinal; register;
label err;
asm
  not eax
  and eax, 1
  jz  err
  ret
  err:
  xor eax, eax
end;

// compiled
asmtest:
  not eax
  and eax, $01
  jz +$01
  ret
  xor eax, eax
  mov eax, edx  // !!!
  ret

似乎与label err. 如果我删除它,我就不会得到这个mov eax, *部分。

为什么会这样?


对Quality Central进行了错误报告。

4

2 回答 2

7

实用的建议是:不要在 asm 代码中使用 label 关键字,使用 @@-prefixed 标签:

function f(x: Cardinal): Cardinal; register;
asm
  not eax
  mov edx,eax
  shr edx, 1
  and eax, edx
  bsf ecx, eax
  jz  @@err
  mov eax, 1
  shl eax, cl
  mov edx, eax
  add edx, edx
  or  eax, edx
  ret
@@err:
  xor eax, eax
end;

更新

我没有在Basm 区域找到错误报告。它看起来像一个错误,但我使用 BASM 多年,从未想过以这种方式使用标签关键字。事实上,我从来没有在 Delphi 中使用过 label 关键字。:)

于 2010-03-07T09:33:40.620 回答
1

嗯......当时,在 Delphi 手册中,它曾经说过编译器优化和类似的疯狂:


编译器仅为嵌套例程、具有局部变量的例程和具有堆栈参数的例程生成堆栈帧

例程的自动生成的初始化和完成代码包括:

PUSH    EBP              ; If Locals <> 0 or Params <> 0
MOV     EBP,ESP          ; If Locals <> 0 or Params <> 0
SUB     ESP,Locals       ; If Locals <> 0
    ...
MOV     ESP,EBP          ; If Locals <> 0
POP     EBP              ; If Locals <> 0 or Params <> 0
RET     Params           ; Always

如果局部变量包含变体、长字符串或接口,它们会使用 Null 进行初始化,但之后不会最终确定。

Locals 是局部变量的大小,Params 是参数的大小。如果 Locals 和 Params 都为 Null,则不会生成 Init-Code,并且 Finalizationcode 仅包含 RET-Intruction。


也许这与这一切有关......

于 2010-04-01T00:18:54.427 回答