使用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并盲目地检索他的第一件事发现不是真正的程序员。
答案 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
函数使用该调用约定,则编译器可以通过多种方式实现该堆栈布局:
您期望的方式,即立即评估和推送每个参数:
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
另一点需要指出的是,你的第一个参数是复合表达式。有一个函数调用和一个比较。没有什么可以保证这两个部分是连续执行的。编译器可能首先调用First
和ReturnFieldI
来调用函数调用,然后将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”调用约定(无论如何都需要重写函数)但是如果编译器无法明确保证评估顺序,则依赖于此类代码总是很危险的。强加评估订单可能会大大减少可用的优化次数。