Delphi 编译器指令反向求值参数

发布于 2024-09-06 01:40:51 字数 626 浏览 8 评论 0原文

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

白云悠悠 2024-09-13 01:40:51

表达式的求值顺序通常未定义。 (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等于0,对ReturnFieldI的调用仍然会被评估。所有实际参数在调用使用它们的函数之前都会被完全评估。

无论如何,改变 Math.pas 对你没有帮助。它不控制其实际参数的计算顺序。当它看到它们时,它们已经被计算为布尔值和整数;它们不再是可执行表达式。


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

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

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

    push (DB.First = 0)
    推送 DB.ReturnFieldI('awesomedata1')
    调用 IfThen
    
  2. 从右到左计算参数并将结果存储在临时变量中,直到它们被推送:

    tmp1 := DB.ReturnFieldI('awesomedata1')
    tmp2 := (DB.First = 0)
    推tmp2
    推tmp1
    调用 IfThen
    
  3. 首先分配堆栈空间,然后以任何方便的顺序进行计算:

    <前><代码>子特别是,8
    mov [esp], DB.ReturnFieldI('awesomedata1')
    mov [esp + 4], (DB.First = 0)
    调用 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 返回值与零进行比较。

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.

缺⑴份安定 2024-09-13 01:40:51

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

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;

The calling convention affects the way they are evaluated.
There is not a compiler define to control this.

Pascal is the calling convention you would have to use to get this behavior.

Although I would personally never depend on this type of behavior.

The following example program demonstrates how this works.

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.

This would require you to write your own IfThen Functions.

If you really want this to be a one liner you really can do that in Delphi. I just think it looks ugly.

If (DB.First = 0) then result :=  DB.ReturnFieldI('awesomedata1') else result := 0;
若水般的淡然安静女子 2024-09-13 01:40:51

您不能将查询更改为只有一个结果,从而避免执行“第一个”命令吗?
就像:

SELECT TOP 1 awesomedata1 from awesometable 

在 Access 中...

Can't you change your query to have only one result so avoid to do the 'First' command ?
Just like :

SELECT TOP 1 awesomedata1 from awesometable 

In Access...

萌逼全场 2024-09-13 01:40:51

AFAIK 没有编译器指令来控制这一点。除非您使用 stdcall/cdecl/safecall 约定,否则参数会在堆栈上从左到右传递,但由于默认寄存器约定也可以在寄存器中传递参数,因此可能会出现稍后计算参数并将其放入寄存器中的情况就在通话之前。由于只有符合条件的参数的寄存器顺序是固定的(EAX、EDX、ECX),因此可以按任何顺序加载寄存器。您可以尝试强制使用“pascal”调用约定(无论如何,您都需要重写该函数),但恕我直言,如果编译器无法明确保证评估顺序,那么依赖此类代码总是很危险的。并且施加评估顺序可能会大大减少可用优化的数量。

AFAIK there is no compiler directive to control this. Unless you use the stdcall/cdecl/safecall conventions, parameters are passed left to right on the stack, but because the default register convention can pass parameters in the registers as well, it could happen that a parameter is calculated later an put in a register just before the call. And because only the register order is fixed (EAX, EDX, ECX) for parameters that qualify, registers can be loaded in any order. You could try to force a "pascal" calling convention (you'd need to rewrite the function, anyway) but IMHO is always dangerous to rely on such kind of code, if the compiler can't explicitly guarantee the order of evaluation. And imposing an evaluation order may greatly reduce the number of optimizations available.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文