2010-06-16 13 views
5

Estaba realmente impresionado con este delphi two liner usando la función IFThen de Math.pas. Sin embargo, primero evalúa DB.ReturnFieldI, lo cual es desafortunado porque necesito llamar a DB.first para obtener el primer registro.Directiva Delphi Compiler para Evaluar Argumentos en Reversa

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

(como una aclaración sin sentido, porque tengo tantas buenas respuestas ya. Me olvidé de mencionar que 0 es el código que DB.First devoluciones si que tiene algo en él, no podría haber tenido sentido de lo contrario)

Obviamente esto no es tan importante, ya que podría hacerlo funcionar con cinco forros robustos. Pero todo lo que necesito para que esto funcione es que Delphi evalúe DB.primero primero y DB.ReturnFieldI segundo. No quiero cambiar math.pas y no creo que esto me obligue a hacer ifthen sobrecargado porque hay como 16 funciones ifthen.

Solo déjame saber cuál es la directiva del compilador, si hay una forma aún mejor de hacerlo, o si no hay forma de hacerlo, y cualquiera cuyo procedimiento sea llamar a db.first y recuperar a ciegas lo primero él encuentra que no es un programador real.

+6

Incluso si existe la posibilidad, ¡no hagas esto! Si necesita que la llamada "Primero" sea la primera, entonces codifíquela de esa manera. En mi humilde opinión, en esta situación solo un 'si' explícito es razonable. –

+0

Noto su humilde opinión, pero ¿no cree que la función If That está justo al revés para empezar? Si no fuera por ese parámetro opcional al final, siempre sería mucho más útil decir IfThen (TrueResult, FalseResult, BooleanFunction). Según su razonamiento, ¿podría decir "if (funciónA y función B) entonces comenzar fin"; siempre debe escribirse "if functiona if if functionb then begin end;" ? –

+0

No necesariamente, porque la evaluación de acceso directo booleana es bien conocida, mientras que la orden de evaluación de los argumentos de función no lo es. Si, por lo general, es inútil porque ** siempre ** evalúa ** todos ** argumentos que en mi experiencia no se desean, lo que más deseo ** o ** es la expresión verdadera ** o ** la expresión falsa que se ejecutará . –

Respuesta

12

El orden de evaluación de las expresiones es comúnmente undefined. (C y C++ son la misma forma. Java siempre evalúa de izquierda a derecha). El compilador no ofrece control sobre él. Si necesita evaluar dos expresiones en un orden específico, escriba su código de manera diferente. Realmente no me preocuparía la cantidad de líneas de código. Las líneas son baratas; usa tantos como necesites Si usted se encuentra utilizando este patrón menudo, escribir una función que envuelve todo:

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

su código original, probablemente no habría sido lo que quería, incluso si el orden de evaluación eran diferentes. Incluso si DB.Firstno fuera igual a cero, la llamada a ReturnFieldI todavía se evaluaría. Todos los parámetros reales se evalúan completamente antes de invocar la función que los usa.

Cambiar Math.pas no te ayudaría de todos modos. No controla en qué orden se evalúan sus parámetros reales. Para cuando los ve, ya se han evaluado hasta un valor booleano y un número entero; Ya no son expresiones ejecutables.


La convención de llamadas puede afectar el orden de evaluación, pero todavía no hay garantía. El orden en que los parámetros se insertan en la pila no necesita coincidir con el orden en el que se determinaron esos valores.De hecho, si encuentra que stdcall o cdecl le dan su orden de evaluación deseada (de izquierda a derecha), entonces están siendo evaluados en el orden inverso del que se les pasó.

La pascal convención de llamadas pasa argumentos de izquierda a derecha en la pila. Eso significa que el argumento de la izquierda es el que se encuentra en la parte inferior de la pila, y el argumento de la derecha está en la parte superior, justo debajo de la dirección de retorno. Si la función IfThen usó esa convención de llamada, hay varias maneras de que el compilador podría lograr que la disposición de pila:

  1. de la forma esperada, y es que cada argumento se evalúa y se empujó de inmediato:

    push (DB.First = 0) 
    push DB.ReturnFieldI('awesomedata1') 
    call IfThen 
    
  2. evaluar los argumentos de derecha a izquierda y almacenar los resultados en los temporales hasta que son empujados:

    tmp1 := DB.ReturnFieldI('awesomedata1') 
    tmp2 := (DB.First = 0) 
    push tmp2 
    push tmp1 
    call IfThen 
    
  3. Allo espacio de pila cado en primer lugar, y evaluar en el orden que sea conveniente:

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

en cuenta que IfThen recibe los valores de los argumentos en el mismo orden en los tres casos, pero las funciones no necesariamente llama en ese orden

La convención de llamada de registro predeterminada también pasa los argumentos de izquierda a derecha, pero los primeros tres argumentos que se ajustan se pasan en los registros. Los registros utilizados para pasar argumentos, sin embargo, son también los registros más comúnmente utilizados para evaluar expresiones intermedias. El resultado de DB.First = 0 debía pasarse en el registro EAX, pero el compilador también necesitaba ese registro para llamar al ReturnFieldI y para llamar al First. Probablemente fue un poco más conveniente evaluar la segunda función en primer lugar, de esta manera:

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 

Otra cosa a destacar es que su primer argumento es una expresión compuesta. Hay una llamada a función y una comparación. No hay nada que garantice que esas dos partes se realicen consecutivamente. El compilador puede obtener primero las llamadas de función llamando al First y ReturnFieldI, y luego comparar el valor de retorno First con cero.

+0

Esto es lo que hago, pero con un tercer parámetro opcional que me permite cambiar el valor de retorno predeterminado si así lo deseo. –

+0

Puede agregar un parámetro opcional a la función anterior para que funcione como se espera. –

+0

Oh, tienes razón, mi código habría funcionado, pero no por ninguna razón lógica. No entiendo, sin embargo, por qué no puedo confiar en CDECL y STDCALL para evaluar las funciones del parámetro en la forma en que dicen que se pasarán. Suponiendo que soy 100% Delphi, ¿cuál es el daño al engañar al compilador en la evaluación de las expresiones de la manera que me parece conveniente? –

1

¿No puede cambiar su consulta para tener un solo resultado, así que evite hacer el comando 'Primero'? Al igual que :

SELECT TOP 1 awesomedata1 from awesometable 

En Access ...

0

yo sepa no hay ninguna directiva de compilación para controlar esto. A menos que utilice las convenciones stdcall/cdecl/safecall, los parámetros se pasan de izquierda a derecha en la pila, pero debido a que la convención de registro predeterminada también puede pasar parámetros en los registros, podría ocurrir que un parámetro se calcule más tarde y se coloque en un registro justo antes de la llamada. Y como solo se corrige el orden de registro (EAX, EDX, ECX) para los parámetros que califican, los registros se pueden cargar en cualquier orden. Podrías tratar de forzar una convención de llamadas "pascal" (de todos modos necesitarías reescribir la función) pero en mi humilde opinión siempre es peligroso confiar en ese tipo de código, si el compilador no puede garantizar explícitamente el orden de evaluación. E imponer una orden de evaluación puede reducir en gran medida la cantidad de optimizaciones disponibles.

3

El calling convention afecta la forma en que se evalúan.
No hay una definición de compilador para controlar esto.

Pascal es la convención de llamadas que debería usar para obtener este comportamiento.

Aunque personalmente nunca dependería de este tipo de comportamiento.

El siguiente programa de ejemplo demuestra cómo funciona esto.

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. 

Esto requeriría que para escribir sus propias funciones ifthen.

Si realmente quieres que esto sea un trazador de líneas, puedes hacerlo en Delphi. Solo creo que se ve feo.

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

No, no está controlado por la convención de llamadas. La convención de llamadas controla qué orden se colocan los parámetros en la pila, o en qué registros se almacenan los valores, pero no controla la orden de evaluación. (Podría * afectar * la orden de evaluación, pero eso no es lo mismo que control.) –

+0

Punto válido, he actualizado mi respuesta para reflejar eso. –

+0

Marqué esto como correcto durante un minuto, pero Rob realmente dio en el clavo cuando dijo que DB.returnfieldI aún se evaluará independientemente. Realmente no quiero que eso suceda si DB.first <> 0. Gracias por el código fuente, realmente ayudó a aclarar las diferencias entre esas convenciones. –

Cuestiones relacionadas