5

Math.pas の IFThen 関数を使用したこの Delphi 2 ライナーには、本当に感銘を受けました。ただし、最初のレコードを取得するには DB.first を呼び出す必要があるため、DB.ReturnFieldI が最初に評価されます。

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

(無意味な説明として、私はすでに非常に多くの良い答えを持っているので、0がDB.Firstが何かを持っている場合に返すコードであることを忘れていました。それ以外の場合は意味をなさない可能性があります)

明らかに、これはそれほど大したことではありません。5 つの堅牢なライナーで動作させることができるからです。しかし、これが機能するために必要なのは、Delphi が最初に DB.first を評価し、次に DB.ReturnFieldI を評価することだけです。私は math.pas を変更したくありません。16 個の ifthen 関数があるため、オーバーロードされた ifthen を作成する必要はないと思います。

これを行うためのさらに良い方法がある場合、またはこれを行う方法がなく、手順が db.first を呼び出して最初に見つけたものをやみくもに取得する方法がない場合は、コンパイラ指令が何であるかを教えてください。本物のプログラマー。

4

4 に答える 4

12

一般に、式の評価順序は定義されていません。(C と C++ は同じ方法です。Java は常に左から右に評価されます。) コンパイラはそれを制御しません。2 つの式を特定の順序で評価する必要がある場合は、コードを別の方法で記述します。コードの行数はあまり気にしません。回線は安いです。必要な数だけ使用します。このパターンを頻繁に使用していることに気付いた場合は、すべてをラップする関数を作成します。

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、コンパイラがそのスタック レイアウトを実現できるいくつかの方法があります。

  1. ご想像のとおり、各引数が評価され、すぐにプッシュされます。

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

は 3 つのケースすべてで同じ順序で引数値をIfThen 受け取りますが、関数は必ずしもその順序で呼び出されるとは限りません。

デフォルトのレジスタ呼び出し規約も引数を左から右に渡しますが、適合する最初の 3 つの引数はレジスタに渡されます。ただし、引数を渡すために使用されるレジスタは、中間式を評価するために最も一般的に使用されるレジスタでもあります。の結果をDB.First = 0EAX レジスタに渡す必要がありましたが、コンパイラは と を呼び出すためにそのレジスタも必要としていReturnFieldIましFirstた。次のように、最初に 2 番目の関数を評価する方がおそらく少し便利でした。

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

指摘すべきもう 1 つのことは、最初の引数が複合式であることです。関数呼び出しと比較があります。これら 2 つの部分が連続して実行されることを保証するものは何もありません。Firstコンパイラは、最初にandを呼び出して関数呼び出しを邪魔にならないようにし、その後、戻り値をゼロReturnFieldIと比較する場合があります。First

于 2010-06-16T15:47:15.513 に答える
3

呼び出し規約は、それらが評価される方法に影響します。
これを制御するコンパイラ定義はありません。

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;
于 2010-06-16T15:59:57.457 に答える
1

'First' コマンドを実行しないように、クエリを変更して結果を 1 つだけにすることはできませんか? と同じように :

SELECT TOP 1 awesomedata1 from awesometable 

アクセスで...

于 2010-06-16T15:36:24.510 に答える
0

私の知る限り、これを制御するコンパイラ ディレクティブはありません。stdcall/cdecl/safecall 規則を使用しない限り、パラメーターはスタック上で左から右に渡されますが、既定のレジスタ規則ではパラメーターをレジスターにも渡すことができるため、後でパラメーターが計算されてからレジスターに入れられる可能性があります。電話の直前。また、修飾するパラメーターのレジスター順序のみが固定されている (EAX、EDX、ECX) ため、レジスターは任意の順序でロードできます。「パスカル」呼び出し規約を強制しようとすることもできますが (とにかく関数を書き直す必要があります)、コンパイラが評価の順序を明示的に保証できない場合、そのような種類のコードに依存することは常に危険です。また、評価順序を課すと、利用可能な最適化の数が大幅に減少する可能性があります。

于 2010-06-16T15:37:29.593 に答える