Domanda

Sono rimasto davvero colpito da questo Delphi Two Liner utilizzando la funzione IFThen di Math.pas.Tuttavia, valuta prima DB.ReturnFieldI, il che è un peccato perché devo chiamare DB.first per ottenere il primo record.

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

(come chiarimento inutile, perché ho già tante buone risposte.Ho dimenticato di dire che 0 è il codice restituito da DB.First se contiene qualcosa, altrimenti potrebbe non avere senso)

Ovviamente questo non è un grosso problema, dato che potrei farlo funzionare con cinque robusti rivestimenti.Ma tutto ciò di cui ho bisogno perché funzioni è che Delphi valuti prima DB.first e poi DB.ReturnFieldI.Non voglio cambiare math.pas e non penso che questo mi giustifichi di creare un ifthen sovraccarico perché ci sono circa 16 funzioni ifthen.

Fammi solo sapere qual è la direttiva del compilatore, se esiste un modo ancora migliore per farlo, o se non c'è modo di farlo e chiunque la cui procedura sia chiamare db.first e recuperare ciecamente la prima cosa che trova non lo è un vero programmatore.

È stato utile?

Soluzione

L'ordine di valutazione delle espressioni è comunemente non definito.(C e C++ sono allo stesso modo.Java valuta sempre da sinistra a destra.) Il compilatore non offre alcun controllo su di esso.Se hai bisogno che due espressioni vengano valutate in un ordine specifico, scrivi il codice in modo diverso.Non mi preoccuperei davvero del numero di righe di codice.Le linee sono economiche;usane quanti ne hai bisogno.Se ti ritrovi a utilizzare spesso questo pattern, scrivi una funzione che concluda il tutto:

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

Il tuo codice originale probabilmente non sarebbe stato quello che volevi, anche se l'ordine di valutazione fosse stato diverso.Anche se DB.First non lo era uguale a zero, la chiamata a ReturnFieldI sarebbe comunque da valutare.Tutti i parametri effettivi vengono valutati completamente prima di richiamare la funzione che li utilizza.

Cambiare Math.pas non ti aiuterebbe comunque.Non controlla l'ordine in cui vengono valutati i suoi parametri effettivi.Nel momento in cui li vede, sono già stati valutati fino a un valore booleano e un numero intero;non sono più espressioni eseguibili.


La convenzione di chiamata può influenzare l'ordine di valutazione, ma non esiste ancora alcuna garanzia.Non è necessario che l'ordine in cui i parametri vengono inseriti nello stack corrisponda all'ordine in cui sono stati determinati tali valori.Infatti, se trovi che stdcall o cdecl ti danno l'ordine di valutazione desiderato (da sinistra a destra), allora vengono valutati nel ordine inverso di quello con cui sono passati.

IL pascal la convenzione di chiamata passa gli argomenti da sinistra a destra nello stack.Ciò significa che l'argomento più a sinistra è quello in fondo allo stack, mentre l'argomento più a destra è in alto, appena sotto l'indirizzo del mittente.Se la IfThen La funzione utilizzava quella convenzione di chiamata, ci sono diversi modi in cui il compilatore potrebbe ottenere quel layout dello stack:

  1. Nel modo previsto, ovvero che ogni argomento venga valutato e inviato immediatamente:

    push (DB.First = 0)
    push DB.ReturnFieldI('awesomedata1')
    call IfThen
    
  2. Valuta gli argomenti da destra a sinistra e memorizza i risultati in temporanei finché non vengono inviati:

    tmp1 := DB.ReturnFieldI('awesomedata1')
    tmp2 := (DB.First = 0)
    push tmp2
    push tmp1
    call IfThen
    
  3. Assegna prima lo spazio nello stack e valuta nell'ordine conveniente:

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

Notare che IfThen riceve l'argomento ha i valori nello stesso ordine in tutti e tre i casi, ma le funzioni non sono necessariamente chiamate in quell'ordine.

Anche la convenzione di chiamata predefinita dei registri passa gli argomenti da sinistra a destra, ma i primi tre argomenti adatti vengono passati nei registri.I registri utilizzati per passare gli argomenti, tuttavia, sono anche i registri più comunemente utilizzati per valutare le espressioni intermedie.Il risultato di DB.First = 0 doveva essere passato nel registro EAX, ma anche il compilatore aveva bisogno di quel registro per la chiamata ReturnFieldI e per chiamare First.Probabilmente era un po' più conveniente valutare prima la seconda funzione, in questo modo:

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

Un'altra cosa da sottolineare è che il tuo primo argomento è un'espressione composta.C'è una chiamata di funzione e un confronto.Non c'è nulla che garantisca che queste due parti vengano eseguite consecutivamente.Il compilatore potrebbe prima eliminare le chiamate di funzione chiamando First E ReturnFieldI, e poi confrontare il First restituire valore rispetto a zero.

Altri suggerimenti

La convenzione chiamando influenza il modo in cui vengono valutati.
Non c'è un compilatore definire per controllare questo.

Pascal è la convenzione di chiamata si dovrebbe utilizzare per ottenere questo comportamento.

Anche se io personalmente non dipendere da questo tipo di comportamento.

Il seguente programma di esempio mostra come funziona.

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.

Questo richiederebbe di scrivere i propri Funzioni ifthen.

Se si vuole veramente che questo sia un uno di linea è davvero possibile farlo in Delphi. Penso solo che sembra brutto.

If (DB.First = 0) then result :=  DB.ReturnFieldI('awesomedata1') else result := 0;

Non riesci a modificare la tua ricerca di avere un solo risultato in modo da evitare di fare il comando 'prima'? Proprio come:

SELECT TOP 1 awesomedata1 from awesometable 

In Access ...

Per quanto ne sappia non v'è alcuna direttiva del compilatore per controllare questo. A meno che non si utilizza il stdcall / cdecl / safecall convenzioni, i parametri sono passati da sinistra a destra sullo stack, ma perché la convenzione di registro di default può passare parametri nei registri così, potrebbe accadere che un parametro viene calcolato dopo, un put in un registro poco prima della chiamata. E perché solo l'ordine registro è fisso (EAX, EDX, ECX) per i parametri che qualificano, registri possono essere caricati in qualsiasi ordine. Si potrebbe provare a forzare un "pascal" convenzione di chiamata (avresti bisogno di riscrivere la funzione, comunque) ma secondo me è sempre pericoloso fare affidamento su questo tipo di codice, se il compilatore non può garantire in modo esplicito l'ordine di valutazione. E l'imposizione di un ordine di valutazione può ridurre notevolmente il numero di ottimizzazioni disponibili.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top