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

Posted

技术标签:

【中文标题】用于反向评估参数的 Delphi 编译器指令【英文标题】:Delphi Compiler Directive to Evaluate Arguments in Reverse 【发布时间】:2010-06-16 15:03:48 【问题描述】:

我对这个使用 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 并盲目检索他的第一件事的人finds 不是真正的程序员

【问题讨论】:

即使有可能——也不要这样做!如果您需要首先调用First,然后像这样编码。恕我直言,在这种情况下,只有明确的 if 是合理的。 我注意到你的拙见,但你不认为 IfThen 函数一开始只是倒退吗?如果最后没有那个可选参数,那么说 IfThen(TrueResult, FalseResult, BooleanFunction) 总是更有用。根据您的推理,您会说“如果(函数A和函数B)然后开始结束;”应该总是写成“if functiona then if functionb then begin end;” ? 不一定,因为布尔快捷方式评估是众所周知的,而函数参数的评估顺序则不是。 IfThen 通常是没用的,因为它总是评估 all 参数,根据我的经验,这是不想要的 - 我主要想要 either True-expression 要执行的 False 表达式。 【参考方案1】:

表达式的求值顺序通常是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 为您提供了所需的评估顺序(从左到右),那么它们将按照与它们一起传递的顺序的相反顺序进行评估。 p>

pascal 调用约定在堆栈上从左到右传递参数。这意味着最左边的参数是堆栈底部的参数,而最右边的参数位于顶部,就在返回地址的下方。如果IfThen 函数使用该调用约定,编译器可以通过多种方式实现该堆栈布局:

    您所期望的方式,即每个参数都被评估并立即推送:

    push (DB.First = 0)
    push DB.ReturnFieldI('awesomedata1')
    call IfThen
    

    从右到左评估参数并将结果存储在临时变量中,直到它们被推送:

    tmp1 := DB.ReturnFieldI('awesomedata1')
    tmp2 := (DB.First = 0)
    push tmp2
    push tmp1
    call IfThen
    

    首先分配堆栈空间,然后以方便的顺序进行评估:

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

请注意,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 返回值与零进行比较。

【讨论】:

这就是我所做的,但有第三个可选参数允许我根据需要更改默认返回值。 您可以在上述函数中添加一个可选参数以使其按预期工作。 哦,你是对的,我的代码可以工作,但不是出于任何合乎逻辑的原因。但是我不明白,为什么我不能依靠 CDECL 和 STDCALL 来评估参数中的函数,就像他们说它们将被传入一样。假设我是 100% 的 Delphi,欺骗有什么害处编译器以我觉得方便的方式评估表达式? "在调用使用它们的函数之前,所有实际参数都经过全面评估。"这正是为什么 IF 是 Lisp 上的一种特殊形式,而 不是 一个函数的原因。 嗯,IF 也不是 Delphi 中的函数。问题是用函数 IFTHEN 替换 IF 引起的。【参考方案2】:

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;

【讨论】:

不,它不受调用约定的控制。调用约定控制将参数放入堆栈的顺序,或将值存储在哪些寄存器中,但它不控制评估顺序。 (它可能影响评估顺序,但这与控制不同。) 我将其标记为正确一分钟,但当 Rob 说 DB.returnfieldI 无论如何都会被评估时,他真的一针见血。如果 DB.first 0,我真的不希望发生这种情况。感谢源代码,它确实有助于澄清这些约定之间的差异。【参考方案3】:

您不能将您的查询更改为只有一个结果,从而避免执行“第一个”命令吗? 就像:

SELECT TOP 1 awesomedata1 from awesometable 

访问中...

【讨论】:

【参考方案4】:

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

【讨论】:

以上是关于用于反向评估参数的 Delphi 编译器指令的主要内容,如果未能解决你的问题,请参考以下文章

[转]Delphi预编译指令总结

Delphi中预编译指令

Delphi 区域编译器指令 - 向后兼容的单元文件?

短字符串的 Delphi 编译器指令不起作用?

绑定到 Delphi XE4 编译器的条件编译器指令是啥?

编译器指令 WIN32 和 CPUX86、WIN64 和 CPUX64 在 Delphi 中是不是可以互换?