¿Por qué estaba mezclando declaraciones y códigos prohibidos hasta C99?

Recientemente me convertí en asistente de profesor de un curso universitario que enseña principalmente C. El curso se estandarizó en C90, principalmente debido al amplio respaldo del comstackdor. Uno de los conceptos muy confusos para los novatos C con experiencia previa en Java es la regla de que las declaraciones de variables y el código no se pueden mezclar dentro de un bloque (statement compuesta).

Esta limitación finalmente se eliminó con C99, pero me pregunto: ¿Alguien sabe por qué estaba allí en primer lugar? ¿Simplifica el análisis de scope variable? ¿Le permite al progtwigdor especificar en qué puntos de la ejecución del progtwig debe crecer la stack para nuevas variables?

Supongo que los diseñadores de idiomas no habrían agregado tal limitación si no tuviera ningún propósito en absoluto.

Al comienzo de C, la memoria disponible y los recursos de la CPU eran realmente escasos. Por lo tanto, tuvo que comstackrse realmente rápido con requisitos mínimos de memoria.

Por lo tanto, el lenguaje C ha sido diseñado para requerir solo un comstackdor muy simple que comstack rápidamente. Esto a su vez conduce al concepto de ” comstackdor de un solo pase “: el comstackdor lee el archivo fuente y lo traduce todo en código ensamblador lo más pronto posible, generalmente mientras lee el archivo fuente. Por ejemplo: cuando el comstackdor lee la definición de una variable global, el código apropiado se emite inmediatamente.

Este rasgo es visible en C hasta el día de hoy:

  • C requiere “declaraciones avanzadas” de todo y de todo. Un comstackdor de múltiples pasos podría mirar hacia adelante y deducir las declaraciones de variables de funciones en el mismo archivo por sí mismo.
  • Esto a su vez hace que los archivos *.h necesarios.
  • Al comstackr una función, el diseño del marco de la stack debe calcularse lo antes posible; de ​​lo contrario, el comstackdor debe hacer varias pasadas sobre el cuerpo de la función.

Hoy en día, ningún comstackdor de C serio aún es de “un solo pase”, porque muchas optimizaciones importantes no se pueden realizar en una sola pasada. Un poco más se puede encontrar en Wikipedia .

El cuerpo estándar se demoró durante bastante tiempo para relajar ese punto de “paso único” con respecto al cuerpo de la función. Supongo que otras cosas fueron más importantes.

Fue así porque siempre se había hecho de esa manera, hacía que los comstackdores de escritura fueran un poco más fáciles, y nadie realmente había pensado en hacerlo de otra manera. Con el tiempo, las personas se dieron cuenta de que era más importante favorecer que los usuarios del lenguaje tuvieran una vida más fácil que los escritores del comstackdor.

Supongo que los diseñadores de idiomas no habrían agregado tal limitación si no tuviera ningún propósito en absoluto.

No asum que los diseñadores de idiomas se propusieron restringir el idioma. A menudo, las restricciones como esta surgen por casualidad y circunstancia.

Supongo que debería ser más fácil para un comstackdor no optimizador producir código eficiente de esta manera:

 int a; int b; int c; ... 

Aunque se declaran 3 variables separadas, el puntero de stack se puede incrementar a la vez sin optimizar estrategias como reordenar, etc.

Compare esto con:

 int a; foo(); int b; bar(); int c; 

Para incrementar el puntero de la stack solo una vez, esto requiere un tipo de optimización, aunque no muy avanzada.

Además, como cuestión de estilo, el primer enfoque fomenta una forma más disciplinada de encoding (no es de extrañar que Pascal también lo haga) al poder ver todas las variables locales en un solo lugar y, finalmente, inspeccionarlas juntas como un todo. Esto proporciona una separación más clara entre el código y los datos.

En los días de la juventud C, cuando Dennis Ritchie trabajaba en ello, las computadoras (PDP-11 por ejemplo) tenían memoria muy limitada (por ejemplo, 64K palabras), y el comstackdor tenía que ser pequeño, por lo que tenía que optimizar muy pocas cosas y muy simplemente. Y en ese momento (codifiqué C en Sun4 / 110 en la era 1986-89), declarar variables de registro fue realmente útil para el comstackdor.

Los comstackdores de hoy son mucho más complejos . Por ejemplo, una versión reciente de GCC (4.6) tiene más de 5 o 10 millones de líneas de código fuente (dependiendo de cómo lo mida), y realiza una gran cantidad de optimizaciones que no existían cuando aparecieron los primeros comstackdores de C.

Y los procesadores de hoy en día también son muy diferentes (no se puede suponer que las máquinas de hoy son como máquinas de la década de 1980, sino miles de veces más rápidas y con miles de RAM y discos adicionales). Hoy en día, la jerarquía de la memoria es muy importante: el error de caché es lo que más hace el procesador (esperando datos de la RAM). Pero en la década de 1980 el acceso a la memoria era casi tan rápido (o tan lento, según los estándares actuales) que la ejecución de una sola instrucción de máquina. Esto es completamente falso hoy: para leer su módulo RAM, su procesador puede tener que esperar varios cientos de nanosegundos, mientras que para los datos en caché L1, puede ejecutar más de una instrucción cada nanosegundo.

Así que no pienses en C como un lenguaje muy cercano al hardware: esto fue cierto en la década de 1980, pero hoy es falso.

oh, pero podrías (en cierto modo) mezclar declaraciones y código, pero declarar nuevas variables estaba limitado al comienzo de un bloque. Por ejemplo, el siguiente es código C89 válido:

 void f() { int a; do_something(); { int b = do_something_else(); } } 

Exigir que las declaraciones de variables aparezcan al comienzo de una statement compuesta no perjudicó la expresividad de C89. Cualquier cosa que legítimamente se pueda hacer usando una statement en el medio del bloque se podría hacer igual de bien agregando un corchete abierto antes de la statement y doblando el corchete de cierre del bloque circundante. Si bien tal requisito a veces puede haber desorientado el código fuente con llaves adicionales de apertura y cierre, tales aparatos no habrían sido solo ruido; habrían marcado el comienzo y el final de los scopes de las variables.

Considere los siguientes dos ejemplos de código:

 {
   do_something_1 ();
   {
     int foo;
     foo = algo1 ();
     if (foo) do_something_1 (foo);
   }
   {
     int barra;
     bar = algo2 ();
     if (bar) do_something_2 (barra);
   }
   {
     int boz;
     boz = algo3 ();
     if (boz) do_something_3 (boz);
   }
 }

y

 {
   do_something_1 ();

   int foo;
   foo = algo1 ();
   if (foo) do_something_1 (foo);

   int barra;
   bar = algo2 ();
   if (bar) do_something_2 (barra);

   int boz;
   boz = algo3 ();
   if (boz) do_something_3 (boz);
 }

Desde una perspectiva en tiempo de ejecución, a la mayoría de los comstackdores modernos probablemente no les importaría si foo tiene un scope sintáctico durante la ejecución de do_something3() , ya que podría determinar que cualquier valor que tuviera antes de esa statement no se usaría después. Por otro lado, alentar a los progtwigdores a escribir declaraciones de una manera que genere un código subóptimo en ausencia de un comstackdor de optimización no es un concepto atractivo.

Además, aunque manejar los casos más simples que involucran declaraciones de variables entremezcladas no sería difícil (incluso un comstackdor de 1970 podría haberlo hecho, si los autores quisieran permitir tales construcciones), las cosas se vuelven más complicadas si el bloque que contiene declaraciones entremezcladas también contiene cualquier goto o tags de case . Los creadores de C probablemente pensaron que permitir el entremezclado de declaraciones variables y otras declaraciones complicaría demasiado los estándares para valer la pena el beneficio.