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

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.

9 comentarios sobre “No uses goto, que yo lo usaré (II). Premio al que lo consiga.”

  1. 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.

  2. 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.

  3. 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!

  4. 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.

  5. 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?

  6. 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.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *