¿Existe un límite para el número de bucles "for" anidados?

79

Dado que todo tiene un límite, me preguntaba si hay un límite para la cantidad de forbucles anidados o, mientras tenga memoria, puedo agregarlos, ¿puede el compilador de Visual Studio crear un programa de este tipo?

Por supuesto, 64 o más forbucles anidados no serían útiles para depurar, pero ¿es factible?

private void TestForLoop()
{
  for (int a = 0; a < 4; a++)
  {
    for (int b = 0; b < 56; b++)
    {
      for (int c = 0; c < 196; c++)
      {
        //etc....
      }
    }
  }
}
Fred Smith
fuente
78
Eres un programador de computadoras: responde tu propia pregunta con programación de computadoras. Escriba un programa que genere, compile y ejecute programas con 1, 2, 4, 8, 16, 32, ... bucles anidados, y aprenderá muy rápidamente si hay un límite.
Eric Lippert
38
# 1. El PO no ha indicado que esto esté relacionado de ninguna manera con el código de producción, a diferencia de un tipo de pregunta hipotética del tipo "qué pasaría si". # 2. No importa cuántos bucles anidados haga el OP mientras experimenta, nunca llegarán al infinito; No importa cuánto lo empuje el OP, la única forma en que alguna vez probará si hay un límite es si se rompe y cuándo.
Panzercrisis
10
"Como todo tiene un límite", no: sin (x) no tiene un límite para x -> oo. Además: si tu premisa es "todo está limitado", ¿por qué estás preguntando "esta cosa está limitada"? La respuesta está en sus suposiciones, lo que hace que el argumento sea completamente inútil y trivial.
Bakuriu
5
@EricLippert Estrictamente hablando, nunca estarías seguro de que no hubiera límite sin importar cuánto tiempo continuaras de esta manera.
Casey
2
"x -> oo" me hizo llorar un poco :) Use "x → ∞"
Manu

Respuestas:

120

Me arriesgaré publicando esto, pero creo que la respuesta es:

Entre 550 y 575

con la configuración predeterminada en Visual Studio 2015

Creé un pequeño programa que genera forbucles anidados ...

for (int i0=0; i0<10; i0++)
{
    for (int i1=0; i1<10; i1++)
    {
        ...
        ...
        for (int i573=0; i573<10; i573++)
        {
            for (int i574=0; i574<10; i574++)
            {
                Console.WriteLine(i574);
            }
        }
        ...
        ...
    }
}

Para 500 bucles anidados, el programa aún se puede compilar. Con 575 bucles, el compilador rescata:

Advertencia AD0001 Analyzer 'Microsoft.CodeAnalysis.CSharp.Diagnostics.SimplifyTypeNames.CSharpSimplifyTypeNamesDiagnosticAnalyzer' arrojó una excepción de tipo 'System.InsufficientExecutionStackException' con el mensaje 'Pila insuficiente para continuar ejecutando el programa de forma segura. Esto puede suceder por tener demasiadas funciones en la pila de llamadas o funciones en la pila que utilizan demasiado espacio en la pila. '.

con el mensaje del compilador subyacente

error CS8078: una expresión es demasiado larga o compleja para compilar

Por supuesto, este es un resultado puramente hipotético . Si el bucle más interno hace más de a Console.WriteLine, es posible que haya menos bucles anidados antes de que se exceda el tamaño de la pila. Además, esto puede no ser un límite estrictamente técnico, en el sentido de que puede haber configuraciones ocultas para aumentar el tamaño máximo de pila para el "Analizador" que se menciona en el mensaje de error, o (si es necesario) para el ejecutable resultante. Esta parte de la respuesta, sin embargo, se deja a las personas que conocen C # en profundidad.


Actualizar

En respuesta a la pregunta en los comentarios :

Me interesaría ver esta respuesta expandida para "probar" experimentalmente si puede poner 575 variables locales en la pila si no se usan en bucles for, y / o si puede poner 575 bucles for no anidados en una sola función

Para ambos casos, la respuesta es: Sí, es posible. Al completar el método con 575 declaraciones generadas automáticamente

int i0=0;
Console.WriteLine(i0);
int i1=0;
Console.WriteLine(i1);
...
int i574=0;
Console.WriteLine(i574);

todavía se puede compilar. Todo lo demás me hubiera sorprendido. El tamaño de pila que se requiere para las intvariables es de solo 2,3 KB. Pero tenía curiosidad y, para probar otros límites, aumenté este número. Finalmente, no se compiló, lo que provocó el error

error CS0204: solo se permiten 65534 locales, incluidos los generados por el compilador

que es un punto interesante, pero ya se ha observado en otra parte: Número máximo de variables en el método

Del mismo modo, 575 bucles no anidados for , como en

for (int i0=0; i0<10; i0++)
{
    Console.WriteLine(i0);
}
for (int i1=0; i1<10; i1++)
{
    Console.WriteLine(i1);
}
...
for (int i574=0; i574<10; i574++)
{
    Console.WriteLine(i574);
}

también se puede compilar. Aquí, también intenté encontrar el límite y creé más de estos bucles. En particular, no estaba seguro de si las variables de bucle en este caso también cuentan como "locales", porque están en las suyas { block }. Pero aún así, más de 65534 no es posible. Finalmente, agregué una prueba que consta de 40000 bucles del patrón

for (int i39999 = 0; i39999 < 10; i39999++)
{
    int j = 0;
    Console.WriteLine(j + i39999);
}

que contenía una variable adicional en el bucle, pero estos parecen contar también como "locales" y no fue posible compilar esto.


Entonces, para resumir: el límite de ~ 550 es causado por la profundidad de anidación de los bucles. Esto también fue indicado por el mensaje de error

error CS8078: una expresión es demasiado larga o compleja para compilar

La documentación del error CS1647 desafortunadamente (pero comprensiblemente) no especifica una "medida" de complejidad, sino que solo brinda consejos pragmáticos.

Hubo un desbordamiento de pila en el compilador procesando su código. Para resolver este error, simplifique su código.

Para enfatizar esto nuevamente: para el caso particular de forbucles profundamente anidados , todo esto es bastante académico e hipotético . Pero la búsqueda web del mensaje de error de CS1647 revela varios casos en los que este error apareció para un código que probablemente no era intencionalmente complejo, sino que se creó en escenarios realistas.

Marco13
fuente
2
@PatrickHofman Sí, la sugerencia "con la configuración predeterminada ..." es importante: esto se refiere a la creación ingenua de un proyecto predeterminado en VS2015. La configuración del proyecto no mostró un parámetro "obvio" para aumentar el límite. Pero independientemente de eso, me pregunto si no hay más límites impuestos por CLI / CLR / lo que sea. La especificación ECMA-335 tiene una sección "III.1.7.4 Debe proporcionar maxstack" , pero soy demasiado novato en C # para decir si esto está realmente relacionado, o incluso para analizar su significado en detalle ...
Marco13
10
No sería la primera vez que Microsoft tuviera un límite de forbucles. Microsoft BASIC tenía un límite de anidamiento de 8.
Mark
3
@Davislor: el mensaje de error se refiere al tamaño de la pila en el compilador , que también es un programa, y ​​que procesa de forma recursiva la construcción anidada en bucle. No está (fuertemente) ligado al número de variables locales en el código bajo compilación.
ruakh
2
¿Se trata sólo de mí? ¡Encuentro esta respuesta absolutamente divertida! También habría pensado 42como respuesta.
cmroanirgo
11
Probablemente valga la pena señalar que 500 bucles anidados suponiendo solo 2 iteraciones por bucle y una iteración por ciclo tomarían aproximadamente 10 ^ 33 googol años (10 ^ 133) para ejecutarse en una computadora de 4 GHz. Para comparar la edad del universo es aproximadamente 1.37 x 10 ^ 10 años. Dado que se predice que los nucleones se desintegrarán en aproximadamente 10 ^ 40 años, puedo decir directamente que el hardware actual no está construido para durar tanto tiempo y es posible que deba reiniciar la computadora mientras tanto.
Maciej Piechotka
40

No hay un límite estricto en la especificación del lenguaje C # o CLR. Su código sería iterativo, en lugar de recursivo, lo que podría conducir a un desbordamiento de pila bastante rápido.

Hay algunas cosas que podrían contar como un umbral, por ejemplo, el intcontador (generalmente) que usaría, que asignaría una entrada inten la memoria para cada bucle (y antes de haber asignado toda su pila con ints ...). Tenga en cuenta que intse requiere el uso de eso y puede reutilizar la misma variable.

Como señaló Marco , el umbral actual está más en el compilador que en la especificación del lenguaje real o en tiempo de ejecución. Una vez que se recodifica, es posible que tenga algunas iteraciones más. Si, por ejemplo, usa Ideone , que de forma predeterminada usa el compilador antiguo, puede obtener más de 1200 forbucles fácilmente.

Sin embargo, tanto for loops es un indicador de mal diseño. Espero que esta pregunta sea puramente hipotética.

Patrick Hofman
fuente
Estoy de acuerdo con esto, pero agregaría que probablemente alcanzaría un límite de tiempo / hardware antes de alcanzar una restricción de software (por ejemplo, se tarda demasiado en compilar, o su código tarda demasiado / demasiados recursos en ejecutarse).
Marshall Tigerus
Los archivos de código de un millón de líneas se compilan bastante rápido, por lo que eso no sería un problema. Además, dado que el código es bastante sencillo para el motor de ejecución, no esperaría demasiado de él en términos de rendimiento.
Patrick Hofman
1
Vamos, no hay límite en el idioma en sí. Que el sistema de archivos tenga solo 20 MB y que el archivo de origen o ejecutable sea demasiado grande no es una limitación real @ Marco13
Patrick Hofman
3
Al contrario de su respuesta, cada bucle de hecho se suma al espacio de pila utilizado. (Está agregando una nueva variable). Por lo tanto, la pila se agotará cuando tenga suficientes variables. Es exactamente el mismo problema que con la recursividad (excepto que el marco de pila de un método ocupará más espacio de pila que solo uno int). Si fueran bucles sin variable de bucle, entonces sería diferente.
Servicio
3
Creo que CPython limita el anidamiento de construcciones de lenguaje en aproximadamente 50. No tiene sentido admitir incluso 1200 bucles anidados ... ya 10 son una clara señal de que algo anda mal con el programa.
Bakuriu
13

Hay un límite para todo C # compilado hasta MSIL. MSIL solo puede admitir 65535 variables locales. Si sus forbucles son como los que mostró en el ejemplo, cada uno requiere una variable.

Es posible que su compilador pueda asignar objetos en el montón para que actúen como almacenamiento de variables locales, evitando este límite. Sin embargo, no estoy seguro de qué tipo de resultados extraños vendrían de eso. Puede haber problemas que surjan con la reflexión que hagan que este enfoque sea ilegal.

Cort Ammon
fuente
6
No necesita ninguna variable para un bucle for.
Patrick Hofman
1
@PatrickHofman Tienes razón, edité en la parte que olvidé incluir
Cort Ammon
@PatrickHofman si no me confunden un bucle for sin variables es equivalente a while(true)lo que formaría un bucle infinito. Si editamos la pregunta a "¿Existe un límite para el número de bucles for anidados en un programa de terminación?" Entonces, ¿podríamos usar el truco de la variable local para formar un límite estricto?
emory
2
@emory Nada te detiene de algunos bucles realmente absurdos que reutilizan variables. No los llamaría bucles for significativos, pero podrían hacerse.
Cort Ammon
1
@emory puede terminar un bucle con a breako hacer que la condición del bucle verifique algo externo, como( ; DateTime.now() < ... ; )
Grisha Levit
7

Entre 800 y 900 para for(;;)bucles vacíos .

Enfoque reflejado de Marco13, excepto for(;;)bucles probados :

for (;;)  // 0
for (;;)  // 1
for (;;)  // 2
// ...
for (;;)  // n_max
{
  // empty body
}

Funcionó para 800 anidados for(;;), pero dio el mismo error que Marco13 encontró al intentar 900 bucles.

Cuando se compila, for(;;)parece bloquear el hilo sin maximizar la CPU; superficialmente, parece actuar como un Thread.Sleep().

Nat
fuente
2
"Termina de ejecutarse casi instantáneamente" - eso es extraño - ¡¿Pensé que for(;;)sería un bucle infinito en C # ?! (No puedo probarlo ahora, pero si resulta ser incorrecto, eliminaré este comentario)
Marco13
@ Marco13: ¡Sí, tienes razón! No estoy seguro de lo que estaba sucediendo en mi código de prueba anteriormente, pero for(;;)se bloquea sin consumir tiempo de CPU.
Nat
Un bucle for (;;) es un bucle infinito, entonces, ¿por qué agregar otro bucle for (;;) u 800 de ellos porque no se ejecutarán? el primero correrá para siempre.
Fred Smith
1
@FredSmith: Los for(;;)bucles están anidados, por lo que todos comienzan hasta el bucle más anidado, que es infinito. Para el primero for(;;)en bloquear, tendría que serlo for(;;){}. En cuanto a por qué hacer esto, fue solo una prueba para ver qué limitaciones tiene la implementación actual de .NET / C #; aparentemente no puede llegar a los 900 for(;;), aunque, como puede notar, no hace nada.
Nat