2010-10-22 17 views
15

Entonces, encontré una pregunta similar here, pero las respuestas son más sobre el estilo y si puede o no hacerlo.Llamar a una función no nula sin usar su valor de retorno. ¿Qué sucede realmente?

Mi pregunta es, ¿qué sucede realmente cuando llamas a una función no nula que devuelve un objeto, pero nunca asignas o usas dicho objeto devuelto? Entonces, menos sobre si puedes o no, porque sé absolutamente que puedes y entiendo la otra pregunta relacionada anteriormente ... ¿qué hace el entorno de compilador/tiempo de ejecución?

Esta no es una pregunta específica de un idioma, pero si responde, especifique a qué idioma se refiere, ya que los comportamientos serán diferentes.

Respuesta

15

Creo que tanto para C# como para Java, el resultado termina en la pila, y el compilador fuerza una instrucción pop para ignorarlo. La publicación del blog de Eric Lippert en "The void is invariant" tiene algo más de información al respecto.

Por ejemplo, considere el siguiente código C#:

using System; 

public class Test 
{ 
    static int Foo() { return 10; } 

    public static void Main() 
    { 
     Foo(); 
     Foo(); 
    } 
} 

La IL generada (por el MS C# 4 compilador) para el método Main es:

.method public hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    .maxstack 8 
    L_0000: call int32 Test::Foo() 
    L_0005: pop 
    L_0006: call int32 Test::Foo() 
    L_000b: pop 
    L_000c: ret 
} 

Nota las llamadas a pop - que desaparecen si convierte Foo en un método nulo.

+0

Quizás C# no es tan inteligente, pero en este ejemplo, ¿el compilador no alinearía el método? Además, ¿qué tan caro es hacer estallar la pila para una llamada así? Es posible que vea una diferencia si esto se repitió 250k veces, pero para su método de llamada promedio no perdería el sueño en una o dos extra pop, a menos que nunca haya utilizado el valor de retorno en ningún lugar del código. – KeithS

+0

¡Gran respuesta, gracias! – DomenicDatti

+0

@KeithS Me preguntaba qué tan caro es también, pero en mi caso particular no es una preocupación. Tengo un método que hace un poco de almacenamiento en caché detrás de escena en múltiples mapas, pero también devuelve la lista solicitada, y otro método que se basa en el almacenamiento en caché y devuelve una lista diferente de uno de los mapas en caché que genera el primer método. Supuse que podría refactorizar la generación del mapa para hacerlo más limpio y evitar este pop extra si fuera necesario. – DomenicDatti

3

Depende un poco de la convención de llamadas que se utilice. Para tipos pequeños/simples, la devolución típicamente ocurrirá en un registro. En este caso, la función escribirá el valor en el registro, pero nada más prestará atención, y la próxima vez que se necesite ese registro (lo que normalmente sucederá bastante rápido) se sobrescribirá con otra cosa.

Para tipos más grandes, el compilador normalmente asignará una estructura para mantener el valor de retorno. No obstante, dónde y cómo exactamente esa asignación variará con el compilador, en algunos casos será una estructura estática y los contenidos serán ignorados y sobreescritos la próxima vez que llame a una función que devuelva el mismo tipo. En otros casos, estará en la pila y, aunque no lo haya utilizado, aún debe asignarse antes de llamar a la función y liberarse posteriormente

+1

¿A qué idioma se refiere? – DomenicDatti

+0

@DomenicDatti: Estaba tratando de mantener la respuesta lo suficientemente general como para aplicar razonablemente bien a los tres. En una máquina virtual, la pila se virtualizará y no podrá usar registros hasta que se haya ejecutado el compilador JIT, pero tanto Java como .NET normalmente usan un JIT. Ninguno de estos tiene ningún efecto importante en el resultado sin embargo. –

1

En .NET, si el objeto que se devuelve es un tipo de referencia, y la aplicación no tiene otras referencias a ese objeto, entonces todavía tendrá un objeto flotando en la memoria hasta que el recolector de basura decida recogerlo.

Esto es potencialmente malo si el objeto que se devuelve pasa a estar reteniendo los recursos. Si implementa la interfaz IDisposable, su código debería estar llamando al método Dispose, pero en este caso nunca se invocará el método Dispose.

EDITAR: corrigió un error tipográfico.

1

Para C++, normalmente, el compilador optimizará el retorno de la variable, convirtiéndola en una función vacía, y si esto permite optimizaciones adicionales, el compilador puede optimizar toda la llamada de función o partes de ella que solo pertenecen al valor de retorno. Para Java y C#, tengo poca idea.

7

¿qué hace el compilador?

El compilador genera una instrucción pop que descarta el resultado de la pila virtual.

¿Qué hace el entorno de tiempo de ejecución?

Suena atascado el código en código que devuelve el valor de retorno en un registro, en lugar de una ubicación de pila. (Normalmente EAX en arquitecturas x86.)

El jitter sabe que el valor no se utilizará, por lo que probablemente genere código que borre el registro. O tal vez solo lo deja dando vueltas en el registro por un tiempo.

¿Qué entorno de tiempo de ejecución le preocupa? Hay muchos de ellos, y todos tienen diferentes nervios.

Cuestiones relacionadas