表达式的求值顺序通常是不明确的。 (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
wasn't等于零,调用ReturnFieldI
还是会被评价的。所有实际参数在调用使用它们的函数之前都会被完全评估。
无论如何,改变 Math.pas 对你没有帮助。它不控制其实际参数的评估顺序。当它看到它们时,它们已经被评估为布尔值和整数;它们不再是可执行表达式。
调用约定可能会影响评估顺序,但仍然不能保证。将参数压入堆栈的顺序不需要与确定这些值的顺序相匹配。事实上,如果您发现 stdcall 或 cdecl 为您提供了所需的评估顺序(从左到右),那么它们将在相反的顺序与他们一起通过的那个人。
The 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
receives在所有三种情况下,参数值的顺序相同,但函数不一定按该顺序调用。
默认的寄存器调用约定也从左到右传递参数,但适合的前三个参数在寄存器中传递。不过,用于传递参数的寄存器也是最常用于计算中间表达式的寄存器。的结果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
另一件需要指出的是,您的第一个参数是复合表达式。有一个函数调用和比较。没有什么可以保证这两个部分是连续执行的。编译器可能会首先通过调用来消除函数调用First
and ReturnFieldI
,然后比较First
返回值为零。