No uses goto, que yo lo usaré (II). Premio al que lo consiga.

Publicado 5/11/2008 16:01 por Rafael Ontivero

En mi anterior entrada hablé sobre dos curiosidades que he encontrado utilizando el Reflector. En concreto, la que más sensación ha causado ha sido la del uso de goto dentro de la implementación de un método en la biblioteca de .NET. Ciertamente quiero en primer lugar romper una lanza a favor, si no de esos gotos en concreto, sí de su uso.

Con esto no quiero decir que haya que utilizarlo al menos una vez en tus programas, de hecho, la mayoría de los que yo hago no lo usan, pero a veces es, si no completamente necesario, al menos sí recomendable hacerlo. Yo el goto lo uso usaba bastante a menudo dentro de interrupciones en el firmware que hago hacía, y a veces lo utilizo en programas normales de PC.

La situación que yo veo más cercana al uso del salto incondicional para un programa general es la de salir de una intensa anidación de bucles. Imaginemos por un momento que estamos dentro de cuatro o cinco niveles de bucle y que queremos salir desde el interior. Algo así:

Bucle
{
    Bucle
    {
        Bucle
        {
            Bucle
            {
                Salir de todos los bucles
            }
        }
    }
}

Si bien podríamos construir los bucles de forma que la salida fuera automática con una solo break interno (por ejemplo, añadiendo a la condición comparadora de cada bucle una comparación con una posible variable de salida), a veces esto no es posible ya que la comparación o bien depende de datos que sólo están disponibles en la anidación interior o bien el tiempo de comparación en cada iteración es excesivo (hemos de tener en cuenta que a cada iteración de un bucle externo se producen n del interno, por lo que ante x bucles el tiempo de ejecución se acerca a nx). En este caso, una instrucción de salto estaría justificada.

También es cierto que pudiera existir un algoritmo diferente y más sencillo, pero chicos, estamos en el Mundo Real™, y a veces ni podemos ser lo buenos programadores que quisiéramos ni tenemos tiempo para ello.

Otra circunstancia menos habitual (pero que se suele producir con bastante frecuencia en el control de dispositivos) es la de ir adquiriendo recursos o activando elementos de forma secuencial y si en algún momento algo falla, se deben ir recorriendo en secuencia más o menos inversa. Si bien se pueden hacer con una secuencia anidada de sentencias if, a veces no resulta tan sencillo por las características del proceso, por lo que la solución más socorrida es la de, ante un fallo, saltar incondicionalmente al punto de vuelta atrás adecuado.

Todo esto viene al caso de los comentarios expuestos en la entrada anterior. Pues bien, el RFOG, que es más cabezota que un ladrillo ignífugo, ha intentado reproducir un código fuente en C# que termine compilado en algún tipo de goto y no lo ha conseguido. Incluso se ha creado un ejemplo que use dicho método Connect(), pero al parecer el SP1 del .NET Framework 3.5 rompe algo (qué raro) en eso de depurar con el código fuente del .NET, así que no ha podido ver si el fuente original de Microsoft trae o no dichos gotos (Parece ser que es un problema generalizado con el SP1, ya que lo único que aparece si buscas en Internet es que nadie puede depurar a través del fuente de .NET una vez aplicado dicho SP).

Y finalmente, el reto

Bueno, pues el reto es ese: conseguir un código fuente sin gotos que, una vez compilado con C#, genere un código que contenga algún goto visto desde el Reflector. La solución o bien la posteáis como comentario o me la enviáis y pondré las mejores dentro de una nueva entrada.

Yo apuesto en contra, apuesto a que el compilador de C# es tan malo que es incapaz de hacer una optimización más o menos seria de ese tipo. Pero me gustaría que me convencierais de lo contrario.

Comparte este post:

Comentarios

# re: No uses goto, que yo lo usaré (II). Premio al que lo consiga.

Wednesday, November 5, 2008 5:23 PM by Juanfra

Si usas un switch al usar Reflector se ven gotos....

# re: No uses goto, que yo lo usaré (II). Premio al que lo consiga.

Wednesday, November 5, 2008 6:03 PM by SergioE

Yo tengo un codigo hecho para asp.net 2.0 hecho en vb que no tiene gotos, es un proyecto que compile en un solo dll con el web deployment projects y que al desensamblar con reflector exportando el codigo fuente (para buscar pronto) a c#, me da una salida que en 3 archivos "fuente" si contiene gotos.

pongo aqui una parte la salida que incluye gotos

switch (num)

       {

           case 0:

               if (this.Page.IsPostBack & this.Panel1.Visible)

               {

                   if (this.PostbackPorError.Text != "True")

                   {

                       this.Panel1.Visible = false;

                       this.Panel2.Visible = true;

                       this.Panel3.Visible = false;

                       this.PostbackPorError.Text = "";

                       break;

                   }

                   this.Panel1.Visible = false;

                   this.Panel2.Visible = true;

                   this.Panel3.Visible = false;

                   this.PostbackPorError.Text = "";

               }

               break;

           case 1:

               this.Panel1.Visible = true;

               goto Label_04F7;

--------------

aqui pongo el original:

Select Case Tipo

           Case 0

               'Verifica el estatus del postback y decide que panel muestra

               If Page.IsPostBack And Panel1.Visible = True Then

                   If PostbackPorError.Text = "True" Then

                       Panel1.Visible = False

                       Panel2.Visible = True

                       Panel3.Visible = False

                       PostbackPorError.Text = ""

                   Else

                       Panel1.Visible = False

                       Panel2.Visible = True

                       Panel3.Visible = False

                       PostbackPorError.Text = ""

                   End If

               End If

               If Page.IsPostBack And Panel2.Visible = True Then

                   If PostbackPorError.Text = "" Then

                       PostbackPorError.Text = ""

                   End If

               End If

...  

Case 1

               Panel1.Visible = True

case 2

...

-----------------

Saludos

SergioE.

# re: No uses goto, que yo lo usaré (II). Premio al que lo consiga.

Thursday, November 6, 2008 12:48 AM by Alfredo Novoa

Break, return y continue son los tipos de goto con los que se supone que nos tenemos que apañar perfectamente. Yo nunca he necesitado un goto con etiqueta.

Si quieres salir de varios bucles anidados normalmente es por que has encontrado lo que buscabas, y en ese caso haces un return devolviendo el valor y listo.

Y si no pues también. Mueves los bucles a un método privado y sales igual con return. Un buen compilador debería de optimizar esto bien.

# re: No uses goto, que yo lo usaré (II). Premio al que lo consiga.

Thursday, November 6, 2008 8:25 AM by Eduard Tomàs i Avellana

Estooo... Rafael, cual es el premio???

Aquí tienes un código en C# (de acuerdo, enrevesado de la ostia, pero eso da igual):

           int x = Convert.ToInt32(args[0]);

           switch (x)

           {

               case 1:

               case 2:

               case 3:

                   if (x==1) foo();

                   baz();

                   if (x == 2) bar();

                   if (x == 1 || x == 2) baz();

                   break;

               case 4:

                   bar();

                   break;

               case 5:

                   baz();

                   break;

               case 6:

               case 7:

               case 8:

                   if (x == 7 || x == 8)

                   {

                       switch (x)

                       {

                           case 7:

                               foo();

                               foo();

                               break;

                           case 8:

                               bar();

                               baz();

                               break;

                       }

                   }

                   if (x == 6 || x == 7)

                   {

                       bar();

                       baz();

                   }

                   bar();

                   baz();

                   break;

               default:

                   foo();

                   bar();

                   break;

           }

Y el código de reflector (última versión) generado:

private static void Main(string[] args)

{

   int x = Convert.ToInt32(args[0]);

   switch (x)

   {

       case 1:

       case 2:

       case 3:

           if (x == 1)

           {

               foo();

           }

           baz();

           switch (x)

           {

               case 2:

                   bar();

                   break;

               case 1:

               case 2:

                   baz();

                   break;

           }

           return;

       case 4:

           bar();

           return;

       case 5:

           baz();

           return;

       case 6:

       case 7:

       case 8:

           switch (x)

           {

               case 7:

               case 8:

                   switch (x)

                   {

                       case 7:

                           foo();

                           foo();

                           goto Label_009B;

                       case 8:

                           bar();

                           baz();

                           goto Label_009B;

                   }

                   break;

           }

           goto Label_009B;

   }

   foo();

   bar();

   return;

Label_009B:

   if ((x == 6) || (x == 7))

   {

       bar();

       baz();

   }

   bar();

   baz();

}

Ves que goto tan bonito??

Conclusión: para switch simples, el compilador NO genera gotos, pero cuando son mas complejos los usa... supongo que para reaprovechar código...

Investigaré un poco más si tengo tiempo, ahora simplemente he ido añadiendo "cases" a lo bestia...

Saludos!

pd: entrada muy interesante!

# re: No uses goto, que yo lo usaré (II). Premio al que lo consiga.

Thursday, November 6, 2008 12:41 PM by Luis Guerrero

Estimado Rafael, es cierto lo que la gente dice, con un switch aparecen gotos (en el reflector) pero hay que poner un poco de luz sobre eso. El caso es que como bien sabrás al hacer un swich lo que estás haciendo es referenciando un dirección dentro del cuerpo del método que estas usando. Hasta aquí todo bien, de hecho cuando se compila las referencias relativas de C# se traducen a referencias de desplazamiento dentro del codigo IL para que después el JIT cuando lo termine de compilar y en base a la dirección que tenga ese modulo en memoria seamos capaces de acceder a esa dirección de memoria que contiene el trozo de código. Véanos un ejemplo de esto:

int value = 3;

           switch (value)

           {

               case 0:

                   {

                       Console.WriteLine(value);

                       break;

                   }

               case 1:

                   {

                       Console.WriteLine(value);

                       break;

                   }

               case 2:

                   {

                       Console.WriteLine(value);

                       break;

                   }

               case 3:

                   {

                       Console.WriteLine(value);

                       break;

                   }

           }

Este es el codigo en IL

.method private hidebysig static void Main(string[] args) cil managed

{

   .entrypoint

   .maxstack 1

   .locals init (

       [0] int32 'value',

       [1] int32 CS$4$0000)

   L_0000: nop

   L_0001: ldc.i4.3

   L_0002: stloc.0

   L_0003: ldloc.0

   L_0004: stloc.1

   L_0005: ldloc.1

   L_0006: switch (L_001d, L_0027, L_0031, L_003b)

   L_001b: br.s L_0045

   L_001d: nop

   L_001e: ldloc.0

   L_001f: call void [mscorlib]System.Console::WriteLine(int32)

   L_0024: nop

   L_0025: br.s L_0045

   L_0027: nop

   L_0028: ldloc.0

   L_0029: call void [mscorlib]System.Console::WriteLine(int32)

   L_002e: nop

   L_002f: br.s L_0045

   L_0031: nop

   L_0032: ldloc.0

   L_0033: call void [mscorlib]System.Console::WriteLine(int32)

   L_0038: nop

   L_0039: br.s L_0045

   L_003b: nop

   L_003c: ldloc.0

   L_003d: call void [mscorlib]System.Console::WriteLine(int32)

   L_0042: nop

   L_0043: br.s L_0045

   L_0045: ret

}

Y este es el código en x86

00000000  push        ebp  

00000001  mov         ebp,esp

00000003  sub         esp,0Ch

00000006  mov         dword ptr [ebp-4],ecx

00000009  cmp         dword ptr ds:[002B30E4h],0

00000010  je          00000017

00000012  call        65A8A411

00000017  xor         edx,edx

00000019  mov         dword ptr [ebp-8],edx

0000001c  xor         edx,edx

0000001e  mov         dword ptr [ebp-0Ch],edx

00000021  mov         dword ptr [ebp-8],3

           switch (value)

00000028  mov         eax,dword ptr [ebp-8]

0000002b  mov         dword ptr [ebp-0Ch],eax

0000002e  mov         eax,dword ptr [ebp-0Ch]

00000031  cmp         eax,4

00000034  jae         0000003D

00000036  jmp         dword ptr [eax*4+008F00E0h]

0000003d  nop              

0000003e  jmp         0000006C

           {

               case 0:

                   {

                       Console.WriteLine(value);

00000040  mov         ecx,dword ptr [ebp-8]

00000043  call        59893CC4

                       break;

00000048  nop              

00000049  jmp         0000006C

                   }

               case 1:

                   {

                       Console.WriteLine(value);

0000004b  mov         ecx,dword ptr [ebp-8]

0000004e  call        59893CC4

                       break;

00000053  nop              

00000054  jmp         0000006C

                   }

               case 2:

                   {

                       Console.WriteLine(value);

00000056  mov         ecx,dword ptr [ebp-8]

00000059  call        59893CC4

                       break;

0000005e  nop              

0000005f  jmp         0000006C

                   }

               case 3:

                   {

                       Console.WriteLine(value);

00000061  mov         ecx,dword ptr [ebp-8]

00000064  call        59893CC4

                       break;

00000069  nop              

0000006a  jmp         0000006C

0000006c  mov         esp,ebp

0000006e  pop         ebp  

0000006f  ret            

Como se puede observar tanto en el codigo IL como en el x86 se saltan a direcciones de memoria dentro del método del cuerpo, se pueden considerara técnicamente goto (teniendo en cuenta q en el codigo inicial no estaban), si pensamos sobre lo que es un goto, tenemos que poner una etiqueta dentro del cuerpo del método a la *altura* que lo queramos y entonces invocarlo. Como bien han comentado por ahí, el reflector no es capaz de volver de la representación del codigo en IL a C#, cuando tiene switch complejos. Lo mismo pasa cuando el codigo está ofuscado. Así que el tema de los goto no es para que Microsoft los use ni dentro de la librería se usen es simplemente que estamos haciendo una mala interpretación de lo que el reflector nos dice, y ahí está el error.

Por cierto el codigo ha sido compilado con las optimizaciones del JIT y sin depuración habilitada.

# re: No uses goto, que yo lo usaré (II). Premio al que lo consiga.

Tuesday, November 11, 2008 7:39 AM by juan

no utilizar goto's es una practica orientada a poder seguir el codigo... es un refinamiento mas alla del de utilizar un lenguaje de alto nivel, que mas da que a la hora de compilarlo se metan gotos?

# re: No uses goto, que yo lo usaré (II). Premio al que lo consiga.

Sunday, November 23, 2008 1:22 AM by Josemi

¿que FirmWare hacias Ontivero?

# re: No uses goto, que yo lo usaré (II). Premio al que lo consiga.

Sunday, November 23, 2008 10:43 AM by Rafael Ontivero

Pues en general relacionado con le sector del recreativo, aunque también caían otras cosas...  Y algún quiosco y cosas de esas. Es que tampoco puedo ser más específico.

Últimamente no he hecho casi nada de eso, pero algo ha caído.

# Tenemos responsabilidad en lo que enseñamos/ mostramos?? … o simplemente nos dejamos llevar por las modas…

Friday, November 28, 2008 3:57 PM by David Daniel Arroyo Zari - Ddaz -

El post que saco hace unos días Jorge   sobre el por que quitar las referencias a VB  hizo