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

有帮助吗?

解决方案

表达式的求值顺序通常是 不明确的. 。(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 为您提供了所需的评估顺序(从左到右),那么它们将在 相反的顺序 与他们一起通过的那个人。

帕斯卡 调用约定在堆栈上从左到右传递参数。这意味着最左边的参数是堆栈底部的参数,最右边的参数位于顶部,就在返回地址的下方。如果 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
    

请注意 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 返回值为零。

其他提示

调用约定影响它们被评估的方式。结果 没有一个编译器定义来控制这一点。

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)功能。

如果你真的希望这是一个班轮你真的能做到这一点的德尔福。我只是觉得它看起来丑陋。

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

无法更改您的查询只能有一个结果,所以要避免做“第一个”的命令? 就像:

SELECT TOP 1 awesomedata1 from awesometable 

在访问...

AFAIK没有编译器指令来控制。除非您使用stdcall / CDECL / safecall约定,参数传递从左到右栈上,但由于默认的寄存器约定可以在寄存器中传递参数,以及,它可能发生,一个参数在寄存器中计算的后一个看跌之前调用。并且因为只有寄存器顺序对于有资格的参数固定(EAX,EDX,ECX),寄存器可以以任何顺序装载。你可以尝试强制“帕斯卡”调用约定(你需要重写功能,反正)但恕我直言总是危险的依靠这样那样的代码,如果编译器不能明确保证评价的顺序。和强加评价顺序可以大大减少可优化的数量。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top