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

Pet*_*ner 5 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'));
Run Code Online (Sandbox Code Playgroud)

(作为一个毫无意义的澄清,因为我已经有了很多好的答案.我忘了提到0是DB.First返回的代码,如果它有内容,否则可能没有意义)

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

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

Rob*_*edy 12

表达式的评估顺序通常是未定义的.(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;
Run Code Online (Sandbox Code Playgroud)

即使评估顺序不同,您的原始代码也可能不是您想要的.即使DB.First 等于零,ReturnFieldI仍然会对呼叫进行评估.在调用使用它们的函数之前,将完全评估所有实际参数.

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


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

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

  1. 您期望的方式,即立即评估和推送每个参数:

    push (DB.First = 0)
    push DB.ReturnFieldI('awesomedata1')
    call IfThen
    
    Run Code Online (Sandbox Code Playgroud)
  2. 从右到左评估参数并将结果存储在临时值中,直到它们被推送为止:

    tmp1 := DB.ReturnFieldI('awesomedata1')
    tmp2 := (DB.First = 0)
    push tmp2
    push tmp1
    call IfThen
    
    Run Code Online (Sandbox Code Playgroud)
  3. 首先分配堆栈空间,并以任何方便的顺序进行评估:

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

请注意,在所有三种情况下都以相同的顺序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
Run Code Online (Sandbox Code Playgroud)

另一点需要指出的是,你的第一个参数是复合表达式.有一个函数调用和一个比较.没有什么可以保证这两个部分是连续执行的.编译器可能会得到函数调用的方式进行第一次调用FirstReturnFieldI,后来比较First反对零返回值.