Delphi编译器指令以反向参数进行评估 [英] Delphi Compiler Directive to Evaluate Arguments in Reverse

查看:134
本文介绍了Delphi编译器指令以反向参数进行评估的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用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,我不认为这是为了保证我的重载,因为这样会有16个功能。



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

解决方案

表达式的评估顺序是通常未定义。 (C和C ++是一样的,Java总是从左到右进行评估。)编译器无法控制它。如果您需要按特定顺序对两个表达式进行评估,请以不同的方式编写代码。我不会真的担心代码行的数量。线条便宜使用尽可能多的你需要的如果您发现自己经常使用此模式,请编写一个将其全部包装的功能:

  function GetFirstIfAvailable(DB:TDatabaseObject; const FieldName:string):整数; 
begin
如果DB.First = 0,则
结果:= DB.ReturnFieldI(FieldName)
else
结果:= 0;
结束

即使评估顺序不同,您的原始代码也不一定是您想要的。即使 DB.First 不等于零,调用 ReturnFieldI 将会仍然被评估。在调用使用它们的函数之前,所有实际参数都将被充分评估。



更改Math.pas不会帮助您。它不会控制其实际参数的计算顺序。在它看到它们的时候,它们已被评估为布尔值和整数;它们不再是可执行表达式。






调用约定可以影响评估顺序,但仍然无法保证。将参数推送到堆栈的顺序不需要与确定这些值的顺序相匹配。事实上,如果你发现stdcall或cdecl给你所需的评估顺序(从左到右),那么他们正在以与他们传递的顺序相反的顺序进行评估。 p>

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


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

      push (DB.First = 0)
    push DB.ReturnFieldI('awesomedata1')
    调用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)
    调用IfThen


通知 IfThen 接收争论在所有三种情况下以相同的顺序排列值,但是这些函数不一定按照这个顺序调用。



默认的注册调用约定也会传递参数,权利,但适合的前三个参数在寄存器中传递。用于传递参数的寄存器也是最常用于评估中间表达式的寄存器。需要在EAX寄存器中传递 DB.First = 0 的结果,但编译器还需要该注册表调用 ReturnFieldI 和调用首先。首先评估第二个函数可能会更方便,如下所示:

 调用DB.ReturnFieldI('awesomedata1') 
mov [ebp - 4],eax //存储结果临时
调用DB.First
测试eax,eax
setz eax
mov edx,[ebp - 4]
调用IfThen

另一件要指出的是你的第一个参数是一个复合表达。有一个函数调用和比较。没有什么可以保证这两个部分是连续执行的。编译器可能首先通过调用 First ReturnFieldI 得到函数调用,然后比较 First 返回值为零。


I was really impressed with this delphi two liner using the IFThen function from Math.pas. However, it evaluates the DB.ReturnFieldI first, which is unfortunate because I need to call DB.first to get the first record.

DB.RunQuery('select awesomedata1 from awesometable where awesometableid = "great"');
result := IfThen(DB.First = 0, DB.ReturnFieldI('awesomedata1'));

(as a pointless clarification, because I've got so many good answers already. I forgot to mention that 0 is the code that DB.First returns if it's got something in it, might not have made sense otherwise)

Obviously this isn't such a big deal, as I could make it work with five robust liners. But all I need for this to work is for Delphi to evaluate DB.first first and DB.ReturnFieldI second. I don't want to change math.pas and I don't think this warrants me making a overloaded ifthen because there's like 16 ifthen functions.

Just let me know what the compiler directive is, if there is an even better way to do this, or if there is no way to do this and anyone whose procedure is to call db.first and blindly retrieve the first thing he finds is not a real programmer.

解决方案

The evaluation order of expressions is commonly undefined. (C and C++ are the same way. Java always evaluates left-to-right.) The compiler offers no control over it. If you need two expressions to be evaluated in a specific order, then write your code differently. I wouldn't really worry about the number of lines of code. Lines are cheap; use as many as you need. If you find yourself using this pattern often, write a function that wraps it all up:

function GetFirstIfAvailable(DB: TDatabaseObject; const FieldName: string): Integer;
begin
  if DB.First = 0 then
    Result := DB.ReturnFieldI(FieldName)
  else
    Result := 0;
end;

Your original code probably wouldn't have been what you wanted, even if evaluation order were different. Even if DB.First wasn't equal to zero, the call to ReturnFieldI would still be evaluated. All actual parameters are fully evaluated before invoking the function that uses them.

Changing Math.pas wouldn't help you anyway. It doesn't control what order its actual parameters are evaluated in. By the time it sees them, they've already been evaluated down to a Boolean value and an integer; they're not executable expressions anymore.


The calling convention can affect evaluation order, but there's still no guarantee. The order that parameters are pushed onto the stack does not need to match the order in which those values were determined. Indeed, if you find that stdcall or cdecl gives you your desired evaluation order (left-to-right), then they are being evaluated in the reverse order of the one they're passed with.

The pascal calling convention passes arguments left-to-right on the stack. That means the leftmost argument is the one at the bottom of the stack, and the rightmost argument is at the top, just below the return address. If the IfThen function used that calling convention, there are several ways the compiler could achieve that stack layout:

  1. The way you expect, which is that each argument is evaluated and pushed immediately:

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

  2. Evaluate arguments right-to-left and store the results in temporaries until they're pushed:

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

  3. Allocate stack space first, and evaluate in whatever order is convenient:

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

Notice that IfThen receives the argument values in the same order in all three cases, but the functions aren't necessarily called in that order.

The default register calling convention also passes arguments left-to-right, but the first three arguments that fit are passed in registers. The registers used to pass arguments, though, are also the registers most commonly used for evaluating intermediate expressions. The result of DB.First = 0 needed to be passed in the EAX register, but the compiler also needed that register for calling ReturnFieldI and for calling First. It was probably a little more convenient to evaluate the second function first, like this:

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

Another thing to point out is that your first argument is a compound expression. There's a function call and a comparison. There's nothing to guarantee that those two parts are performed consecutively. The compiler might get the function calls out of the way first by calling First and ReturnFieldI, and afterward compare the First return value against zero.

这篇关于Delphi编译器指令以反向参数进行评估的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆