代码之家  ›  专栏  ›  技术社区  ›  Peter Turner

Delphi编译器指令,用于反向计算参数

  •  5
  • Peter Turner  · 技术社区  · 14 年前

    我对这个使用Math.pas中IFThen函数的delphi二行程序印象深刻。但是,它首先计算DB.ReturnFieldI,这很不幸,因为我需要调用DB.first来获取第一条记录。

    DB.RunQuery('select awesomedata1 from awesometable where awesometableid = "great"');
    result := IfThen(DB.First = 0, DB.ReturnFieldI('awesomedata1'));
    

    (作为一个毫无意义的澄清,因为我已经得到了这么多好的答案。我忘了提到0是DB.First返回的代码,如果其中包含某些内容,则可能没有意义)

    4 回复  |  直到 14 年前
        1
  •  12
  •   Rob Kennedy    14 年前

    表达式的求值顺序通常是

    function GetFirstIfAvailable(DB: TDatabaseObject; const FieldName: string): Integer;
    begin
      if DB.First = 0 then
        Result := DB.ReturnFieldI(FieldName)
      else
        Result := 0;
    end;
    

    您的原始代码可能不是您想要的,即使求值顺序不同。即使 DB.First 不是的 ReturnFieldI

    不管怎样,改变数学.pas也帮不了你。它不控制实际参数的求值顺序。当它看到它们时,它们已经被计算成一个布尔值和一个整数;它们不再是可执行表达式了。


    调用约定可以影响求值顺序,但仍不能保证。将参数推送到堆栈上的顺序不需要与确定这些值的顺序匹配。实际上,如果您发现stdcall或cdecl提供了所需的求值顺序(从左到右),那么它们将在 倒序 和他们擦肩而过的那个。

    这个 帕斯卡 调用约定在堆栈上从左到右传递参数。这意味着最左边的参数位于堆栈的底部,最右边的参数位于顶部,就在返回地址的下面。如果 IfThen

    1. 您期望的方式是,立即计算并推送每个参数:

      push (DB.First = 0)
      push DB.ReturnFieldI('awesomedata1')
      call IfThen
      
    2. 从右向左计算参数,并将结果存储在临时变量中,直到推送它们:

      tmp1 := DB.ReturnFieldI('awesomedata1')
      tmp2 := (DB.First = 0)
      push tmp2
      push tmp1
      call IfThen
      
    3. 首先分配堆栈空间,然后按方便的顺序进行计算:

      sub esp, 8
      mov [esp], DB.ReturnFieldI('awesomedata1')
      mov [esp + 4], (DB.First = 0)
      call IfThen
      

    注意 接收 在这三种情况下,参数值的顺序都是相同的,但函数不一定按这个顺序调用。

    默认的寄存器调用约定也从左到右传递参数,但适合的前三个参数在寄存器中传递。不过,用于传递参数的寄存器也是最常用于计算中间表达式的寄存器。结果 DB.First = 0 需要在EAX寄存器中传递,但是编译器也需要该寄存器来调用 返回场 谢谢你的来电 First

    call DB.ReturnFieldI('awesomedata1')
    mov [ebp - 4], eax // store result in temporary
    call DB.First
    test eax, eax
    setz eax
    mov edx, [ebp - 4]
    call IfThen
    

    弗斯特 返回场 弗斯特 返回值对零。

        2
  •  3
  •   Robert Love    14 年前

    这个 calling convention 影响评估它们的方式。

    Pascal 是您必须使用的调用约定来获取此行为。

    虽然我个人永远不会依赖这种行为。

    下面的示例程序演示了这是如何工作的。

    program Project2;
    {$APPTYPE CONSOLE}
    uses SysUtils;
    
    function ParamEvalTest(Param : Integer) : Integer;
    begin
      writeln('Param' + IntToStr(Param) + ' Evaluated');
      result := Param;
    end;
    
    procedure TestStdCall(Param1,Param2 : Integer); stdCall;
    begin
      Writeln('StdCall Complete');
    end;
    
    procedure TestPascal(Param1,Param2 : Integer); pascal;
    begin
      Writeln('Pascal Complete');
    end;
    
    procedure TestCDecl(Param1,Param2 : Integer); cdecl;
    begin
      Writeln('CDecl Complete');
    end;
    
    procedure TestSafecall(Param1,Param2 : Integer); safecall;
    begin
      Writeln('SafeCall Complete');
    end;
    
    begin
      TestStdCall(ParamEvalTest(1),ParamEvalTest(2));
      TestPascal(ParamEvalTest(1),ParamEvalTest(2));
      TestCDecl(ParamEvalTest(1),ParamEvalTest(2));
      TestSafeCall(ParamEvalTest(1),ParamEvalTest(2));
      ReadLn;
    end.
    

    如果你真的希望这是一个一行,你真的可以这样做,在德尔福。我只是觉得很难看。

    If (DB.First = 0) then result :=  DB.ReturnFieldI('awesomedata1') else result := 0;
    
        3
  •  1
  •   philnext    14 年前

    就像:

    SELECT TOP 1 awesomedata1 from awesometable 
    

        4
  •  0
  •   user160694 user160694    14 年前

    AFAIK没有编译器指令来控制它。除非使用stdcall/cdecl/safecall约定,否则参数将在堆栈上从左到右传递,但由于默认寄存器约定也可以在寄存器中传递参数,因此可能会在稍后计算参数,并在调用之前放入寄存器。因为只有寄存器顺序是固定的(EAX,EDX,ECX),所以寄存器可以按任何顺序加载。您可以尝试强制执行“pascal”调用约定(无论如何,您需要重写函数),但是如果编译器不能明确地保证求值顺序,那么依赖这种代码总是很危险的。而强制执行求值顺序可能会大大减少可用的优化数量。