2011-06-27 19 views
5

Tengo mucha curiosidad saber por qué exactamente compiladores C89 descargarán en usted cuando intenta mezclar las declaraciones de variables y códigos, así por ejemplo:C89, Mezcla declaraciones de variables y Código

[email protected]:~$ cat test.c 
#include <stdio.h> 

int 
main(void) 
{ 
    printf("Hello World!\n"); 
    int x = 7; 
    printf("%d!\n", x); 
    return 0; 
} 
[email protected]:~$ gcc -std=c89 -pedantic test.c 
test.c: In function ‘main’: 
test.c:7: warning: ISO C90 forbids mixed declarations and code 
[email protected]:~$ 

Sí, puede evitar este tipo de cosas al mantenerse alejado de -pedante. Pero luego su código ya no cumple con los estándares. Y como cualquiera que sea capaz de responder a este mensaje probablemente ya lo sepa, esto no es solo una preocupación teórica. Las plataformas como el compilador C de Microsoft hacen que esto sea rápido en el estándar bajo cualquier circunstancia.

Dada la antigua C es, me imagino que esta característica se debe a algún problema histórico que se remonta a las limitaciones de hardware extraordinarias de la década de los 70, pero no sé los detalles. ¿O estoy totalmente equivocado allí?

+0

Heh. Ha pasado un tiempo, pero recuerdo que está relacionado con la incapacidad de mezclar declaraciones/asignaciones de pila y código en plataformas particulares. Más tarde, se agregaron bloques anidados para permitir una forma limitada de mezcla incluso en esas plataformas (que las trataban como funciones internas, más o menos). Sin embargo, no encuentro referencias, y hace mucho tiempo que no confío plenamente en mi memoria, y esta es la razón por la que esto no está en las respuestas. – geekosaur

+3

¿Por qué no preguntas a dmr? Le envié un correo electrónico (su dirección está públicamente disponible, en su sitio web). Publicaré cualquier respuesta que obtenga aquí ... aunque espero que él cree una cuenta y se responda a sí mismo :-) –

Respuesta

7

El estándar C dijo "no harás", ya que no estaba permitido en los compiladores de C anteriores, que el estándar C89 estandarizado. Fue un paso suficientemente radical para crear un lenguaje que podría usarse para escribir un sistema operativo y sus utilidades. Probablemente, el concepto simplemente no se consideró, no había otro lenguaje en el momento permitido (Pascal, Algol, PL/1, Fortran, COBOL), por lo que C tampoco lo necesitaba. Y probablemente hace que el compilador sea un poco más difícil de manejar (más grande), y los compiladores originales tenían limitaciones de espacio con el código 64 KiB y espacio de datos 64 KiB permitido en la serie PDP 11 (las máquinas grandes, las más pequeñas solo permitían 64 KiB para código y datos, AFAIK). Entonces, la complejidad adicional no era una buena idea.

Era C++ que permitía intercalar las declaraciones y las variables, pero C++ no es, y nunca lo ha sido, C. Sin embargo, C99 finalmente se encontró con C++ (es una característica útil). Lamentablemente, Microsoft nunca implementó C99.

+0

Tengo curiosidad sobre K & R C. En K & R C las declaraciones tuvieron que comenzar al principio de bloque de funciones o fueron declaraciones permitidas al comienzo de cualquier bloque. Quiero decir, fue 'foo() {int i; para (i = 0; i

+0

@Zboson: C pre-estándar permitió definiciones de variables al comienzo de cualquier instrucción compuesta dentro de llaves '{...}'. Hubo límites en la inicialización, pero no en las definiciones per se. El estándar en gran medida siguió la práctica existente en eso. –

0

Es mucho más fácil escribir un compilador para el lenguaje que requiere que todas las variables se declaren al inicio de la función. Algunos lenguajes incluso requieren que se declaren variables en una cláusula específica fuera del código de función (Pascal y Smalltalk vienen a la mente).

La razón es que es más fácil para mapear estas variables de pila (o registros si su compilador es suficientemente inteligente) si son conocidos y no cambian.

cualquier otra declaración (esp. Llamadas a funciones) pueden modificar la pila/registros, por lo que la asignación de variables más complejas.

+0

Pascal permite definiciones de variables al comienzo de un bloque y variables globales fuera de cualquier bloque. –

2

Es similar a la exigencia de funciones para ser declaradas antes de ser utilizados - que permite un compilador de mente simple de operar en una sola pasada, de arriba a abajo, emitiendo código objeto que va.

En este caso particular, el compilador puede pasar por las declaraciones, sumando el espacio de pila requerido. Cuando alcanza la primera instrucción, puede dar salida al código para ajustar la pila, asignando espacio para los locales, inmediatamente antes del inicio del código de función propiamente dicho.

3

Probablemente nunca fue implementado de esa manera, porque nunca fue necesario.

Supongamos que quiere escribir algo como esto en C plano:

int myfunction(int value) 
    { 
    if (value==0) 
     return 0; 
    int result = value * 2; 
    return result; 
    } 

A continuación, puede volver a escribir fácilmente esta en válida C, así:

int myfunction(int value) 
    { 
    int result; 
    if (value==0) 
     return 0; 
    result = value * 2; 
    return result; 
    } 

No hay absolutamente ningún impacto en el rendimiento por primero declarando la variable, luego estableciendo su valor.

Sin embargo, en C++, este ya no es el caso. En el siguiente ejemplo, function2 será más lenta que function1:

double function1(const Factory &factory) 
    { 
    if (!factory.isWorking()) 
     return 0; 
    Product product(factory.makeProduct()); 
    return product.getQuantity(); 
    } 

double function2(const Factory &factory) 
    { 
    Product product; 
    if (!factory.isWorking()) 
     return 0; 
    product = factory.makeProduct(); 
    return product.getQuantity(); 
    } 

En function2 la variable producto tiene que ser construido, incluso cuando la fábrica no está funcionando. Más tarde, la fábrica fabrica el producto y luego el operador de asignación necesita copiar el producto (desde el valor de retorno de makeProduct a la variable del producto). En la función 1, el producto solo se construye cuando la fábrica está funcionando, e incluso entonces, se llama al constructor de copia, no al constructor normal y al operador de asignación.

Sin embargo, esperaría hoy en día que un buen compilador de C++ podría optimizar este código, pero en los primeros compiladores de C++ probablemente este no era el caso.

Un segundo ejemplo es la siguiente:

double function1(const Factory &factory) 
    { 
    if (!factory.isWorking()) 
     return 0; 
    Product &product = factory.getProduct(); 
    return product.getQuantity(); 
    } 

double function2(const Factory &factory) 
    { 
    Product &product; 
    if (!factory.isWorking()) 
     return 0; 
    product = factory.getProduct(); // Invalid. You can't assign to a reference. 
    return product.getQuantity(); 
    } 

En este ejemplo, function2 es simplemente no válido. A las referencias solo se les puede asignar un valor en el momento de la declaración, no más tarde. Esto significa que en este ejemplo, la única forma de escribir código válido es escribir la declaración en el momento en que la variable realmente se inicializa. No antes.

Ambos ejemplos muestran por qué era realmente necesario en C++ para permitir declaraciones de variables después de otras instrucciones ejecutables, y no al principio del bloque como en C. Esto explica por qué esto se agregó a C++ y no a C (y otros idiomas) donde no es realmente necesario.

+0

De manera similar, la mayoría de los compiladores C89 también optimizarán el código y solo asignarán variables en la pila si realmente se usan en la función. – Lundin