Delphi Compiler Directive to Evaluate Arguments in Reverse

前端 未结 4 888
我寻月下人不归
我寻月下人不归 2021-01-13 11:41

I was really impressed with this delphi two liner using the IFThen function from Math.pas. However, it evaluates the DB.ReturnFieldI first, which is unfortunate because I n

相关标签:
4条回答
  • 2021-01-13 11:47

    Can't you change your query to have only one result so avoid to do the 'First' command ? Just like :

    SELECT TOP 1 awesomedata1 from awesometable 
    

    In Access...

    0 讨论(0)
  • 2021-01-13 11:49

    The evaluation order of expressions is commonly undefined. (C and C++ are the same way. Java always evaluates left-to-right.) The compiler offers no control over it. If you need two expressions to be evaluated in a specific order, then write your code differently. I wouldn't really worry about the number of lines of code. Lines are cheap; use as many as you need. If you find yourself using this pattern often, write a function that wraps it all up:

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

    Your original code probably wouldn't have been what you wanted, even if evaluation order were different. Even if DB.First wasn't equal to zero, the call to ReturnFieldI would still be evaluated. All actual parameters are fully evaluated before invoking the function that uses them.

    Changing Math.pas wouldn't help you anyway. It doesn't control what order its actual parameters are evaluated in. By the time it sees them, they've already been evaluated down to a Boolean value and an integer; they're not executable expressions anymore.


    The calling convention can affect evaluation order, but there's still no guarantee. The order that parameters are pushed onto the stack does not need to match the order in which those values were determined. Indeed, if you find that stdcall or cdecl gives you your desired evaluation order (left-to-right), then they are being evaluated in the reverse order of the one they're passed with.

    The pascal calling convention passes arguments left-to-right on the stack. That means the leftmost argument is the one at the bottom of the stack, and the rightmost argument is at the top, just below the return address. If the IfThen function used that calling convention, there are several ways the compiler could achieve that stack layout:

    1. The way you expect, which is that each argument is evaluated and pushed immediately:

      push (DB.First = 0)
      push DB.ReturnFieldI('awesomedata1')
      call IfThen
      
    2. Evaluate arguments right-to-left and store the results in temporaries until they're pushed:

      tmp1 := DB.ReturnFieldI('awesomedata1')
      tmp2 := (DB.First = 0)
      push tmp2
      push tmp1
      call IfThen
      
    3. Allocate stack space first, and evaluate in whatever order is convenient:

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

    Notice that IfThen receives the argument values in the same order in all three cases, but the functions aren't necessarily called in that order.

    The default register calling convention also passes arguments left-to-right, but the first three arguments that fit are passed in registers. The registers used to pass arguments, though, are also the registers most commonly used for evaluating intermediate expressions. The result of DB.First = 0 needed to be passed in the EAX register, but the compiler also needed that register for calling ReturnFieldI and for calling First. It was probably a little more convenient to evaluate the second function first, like this:

    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
    

    Another thing to point out is that your first argument is a compound expression. There's a function call and a comparison. There's nothing to guarantee that those two parts are performed consecutively. The compiler might get the function calls out of the way first by calling First and ReturnFieldI, and afterward compare the First return value against zero.

    0 讨论(0)
  • 2021-01-13 11:50

    The calling convention affects the way they are evaluated.
    There is not a compiler define to control this.

    Pascal is the calling convention you would have to use to get this behavior.

    Although I would personally never depend on this type of behavior.

    The following example program demonstrates how this works.

    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.
    

    This would require you to write your own IfThen Functions.

    If you really want this to be a one liner you really can do that in Delphi. I just think it looks ugly.

    If (DB.First = 0) then result :=  DB.ReturnFieldI('awesomedata1') else result := 0;
    
    0 讨论(0)
  • 2021-01-13 11:51

    AFAIK there is no compiler directive to control this. Unless you use the stdcall/cdecl/safecall conventions, parameters are passed left to right on the stack, but because the default register convention can pass parameters in the registers as well, it could happen that a parameter is calculated later an put in a register just before the call. And because only the register order is fixed (EAX, EDX, ECX) for parameters that qualify, registers can be loaded in any order. You could try to force a "pascal" calling convention (you'd need to rewrite the function, anyway) but IMHO is always dangerous to rely on such kind of code, if the compiler can't explicitly guarantee the order of evaluation. And imposing an evaluation order may greatly reduce the number of optimizations available.

    0 讨论(0)
提交回复
热议问题