用于评估反向参数的Delphi编译器指令

时间:2010-06-16 15:03:49

标签: delphi compiler-directives

使用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返回的代码,如果它有内容,否则可能没有意义)

显然这不是什么大问题,因为我可以使用五个强大的衬垫。但是我需要的就是让Delphi首先评估DB.first,然后再评估DB.ReturnFieldI。我不想改变math.pas而且我认为这不保证我会过度加载ifthen,因为它有16个ifthen函数。

请让我知道编译器指令是什么,如果有更好的方法,或者如果没有办法做到这一点,任何人的程序是调用db.first并盲目地检索他的第一件事发现不是真正的程序员。

4 个答案:

答案 0 :(得分:12)

表达式的评估顺序通常是 undefined 。 (C和C ++是一样的.Java始终从左到右进行评估。)编译器无法控制它。如果需要以特定顺序评估两个表达式,则以不同方式编写代码。我不会真的担心代码行数。线很便宜;尽可能多地使用。如果您经常发现自己使用此模式,请编写一个包装它的函数:

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的调用。在调用使用它们的函数之前,将完全评估所有实际参数。

无论如何,改变Math.pas都无济于事。它不控制其实际参数的评估顺序。当它看到它们时,它们已经被评估为布尔值和整数;它们不再是可执行的表达式了。


调用约定会影响评估顺序,但仍然无法保证。将参数推送到堆栈的顺序不需要与确定这些值的顺序相匹配。实际上,如果您发现stdcall或cdecl为您提供了所需的评估顺序(从左到右),那么它们将在它们传递的逆序中进行评估。

pascal 调用约定在堆栈上从左到右传递参数。这意味着最左边的参数是堆栈底部的参数,最右边的参数位于顶部,就在返回地址的下方。如果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
    
  4. 请注意IfThen 在所有三种情况下以相同的顺序接收参数值,但不一定按该顺序调用函数。

    默认的寄存器调用约定也从左到右传递参数,但前三个适合的参数在寄存器中传递。但是,用于传递参数的寄存器也是最常用于评估中间表达式的寄存器。 DB.First = 0的结果需要在EAX寄存器中传递,但编译器还需要该寄存器来调用ReturnFieldI和调用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
    

    另一点需要指出的是,你的第一个参数是复合表达式。有一个函数调用和一个比较。没有什么可以保证这两个部分是连续执行的。编译器可能首先调用FirstReturnFieldI来调用函数调用,然后将First返回值与零进行比较。

答案 1 :(得分:3)

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.

这将要求您编写自己的IfThen函数。

如果你真的希望这是一个单行程,你真的可以在Delphi中做到这一点。我觉得它看起来很难看。

If (DB.First = 0) then result :=  DB.ReturnFieldI('awesomedata1') else result := 0;

答案 2 :(得分:1)

你不能改变你的查询只有一个结果,所以避免做'第一'命令? 就像:

SELECT TOP 1 awesomedata1 from awesometable 

在Access ...

答案 3 :(得分:0)

AFAIK没有编译器指令来控制它。除非你使用stdcall / cdecl / safecall约定,否则参数会在堆栈中从左向右传递,但由于默认寄存器约定也可以传递寄存器中的参数,因此可能会发生以后计算参数放入寄存器的情况就在电话会议之前。并且因为只有寄存器顺序是固定的(EAX,EDX,ECX)用于符合条件的参数,寄存器可以按任何顺序加载。您可以尝试强制执行“pascal”调用约定(无论如何都需要重写函数)但是如果编译器无法明确保证评估顺序,则依赖于此类代码总是很危险的。强加评估订单可能会大大减少可用的优化次数。