estandar c c99 c89

estandar - c99 shell



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

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 compilador. 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 (declaración 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 alcance variable? ¿Le permite al programador especificar en qué puntos de la ejecución del programa debe crecer la pila 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 compilarse realmente rápido con requisitos mínimos de memoria.

Por lo tanto, el lenguaje C ha sido diseñado para requerir solo un compilador muy simple que compila rápidamente. Esto a su vez conduce al concepto de " compilador de un solo pase ": el compilador 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 compilador 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 compilador 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 compilar una función, el diseño del marco de la pila debe calcularse lo antes posible; de ​​lo contrario, el compilador debe hacer varias pasadas sobre el cuerpo de la función.

Hoy en día, ningún compilador 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.


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

Los compiladores 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 compiladores 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.


Exigir que las declaraciones de variables aparezcan al comienzo de una declaración compuesta no perjudicó la expresividad de C89. Cualquier cosa que legítimamente se pueda hacer usando una declaración en el medio del bloque se podría hacer igual de bien agregando un corchete abierto antes de la declaración 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 alcances de las variables.

Considere los siguientes dos ejemplos de código:

{ do_something_1(); { int foo; foo = something1(); if (foo) do_something_1(foo); } { int bar; bar = something2(); if (bar) do_something_2(bar); } { int boz; boz = something3(); if (boz) do_something_3(boz); } }

y

{ do_something_1(); int foo; foo = something1(); if (foo) do_something_1(foo); int bar; bar = something2(); if (bar) do_something_2(bar); int boz; boz = something3(); if (boz) do_something_3(boz); }

Desde una perspectiva en tiempo de ejecución, a la mayoría de los compiladores modernos probablemente no les importaría si foo tiene un alcance sintáctico durante la ejecución de do_something3() , ya que podría determinar que cualquier valor que tuviera antes de esa declaración no se usaría después. Por otro lado, alentar a los programadores a escribir declaraciones de una manera que genere un código subóptimo en ausencia de un compilador 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 compilador 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 etiquetas 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.


Fue así porque siempre se había hecho de esa manera, hacía que los compiladores 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 compilador.

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

No asuma 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 compilador no optimizador producir código eficiente de esta manera:

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

Aunque se declaran 3 variables separadas, el puntero de pila 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 pila 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 codificación (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.


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(); } }