Durante mucho tiempo pensé que en C, todas las variables tenían que declararse al comienzo de la función. Sé que en C99, las reglas son las mismas que en C ++, pero ¿cuáles son las reglas de ubicación de declaración variable para C89 / ANSI C?
El siguiente código se compila correctamente con gcc -std=c89
y gcc -ansi
:
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
char c = (i % 95) + 32;
printf("%i: %c\n", i, c);
char *s;
s = "some string";
puts(s);
}
return 0;
}
¿No deberían las declaraciones c
y s
causar un error en modo C89 / ANSI?
c
declaration
c89
mcjabberz
fuente
fuente
Respuestas:
Se compila con éxito porque GCC permite la declaración
s
como una extensión GNU, aunque no es parte del estándar C89 o ANSI. Si desea cumplir estrictamente con esos estándares, debe pasar la-pedantic
bandera.La declaración
c
al comienzo de un{ }
bloque es parte del estándar C89; El bloque no tiene que ser una función.fuente
s
es una extensión (desde el punto de vista C89). La declaración dec
es perfectamente legal en C89, no se necesitan extensiones.Para C89, debe declarar todas sus variables al comienzo de un bloque de alcance .
Por lo tanto, su
char c
declaración es válida ya que está en la parte superior del bloque de alcance del bucle for. Pero, lachar *s
declaración debería ser un error.fuente
gcc
. Es decir, no confíe en que un programa puede compilarse para significar que es compatible.La agrupación de declaraciones de variables en la parte superior del bloque es un legado probablemente debido a las limitaciones de los compiladores C primitivos y antiguos. Todos los lenguajes modernos recomiendan y a veces incluso hacen cumplir la declaración de variables locales en el último punto: donde se inicializan por primera vez. Porque esto elimina el riesgo de usar un valor aleatorio por error. Separar la declaración y la inicialización también le impide usar "const" (o "final") cuando pueda.
Desafortunadamente, C ++ sigue aceptando la antigua forma de declaración superior para la compatibilidad con C (una compatibilidad de C se arrastra de muchas otras ...) Pero C ++ intenta alejarse de ella:
C99 comienza a mover C en esta misma dirección.
Si le preocupa no encontrar dónde se declaran las variables locales, significa que tiene un problema mucho mayor: el bloque de cierre es demasiado largo y debe dividirse.
https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions
fuente
int y; ... if (x) { printf("X was true"); y=23;} return y;
...null
, todo-bits-cero suele ser un valor de trampa útil. Además, en los lenguajes que especifican explícitamente que las variables están predeterminadas en todos los bits cero, la confianza en ese valor no es un error . Los compiladores no todavía tienden a ser excesivamente loco con sus "optimizaciones", pero los autores de compiladores siguen tratando de obtener más y más inteligente. Una opción del compilador para inicializar variables con variables pseudoaleatorias deliberadas puede ser útil para identificar fallas, pero el simple hecho de dejar el almacenamiento con su último valor a veces puede enmascarar fallas.Desde un punto de vista de mantenibilidad, en lugar de sintáctico, hay al menos tres líneas de pensamiento:
Declare todas las variables al comienzo de la función para que estén en un solo lugar y pueda ver la lista completa de un vistazo.
Declare todas las variables lo más cerca posible del lugar donde se usaron por primera vez, para saber por qué se necesitan.
Declare todas las variables al comienzo del bloque de alcance más interno, por lo que saldrán del alcance lo antes posible y permitirán que el compilador optimice la memoria y le diga si las usó accidentalmente donde no había pensado.
Por lo general, prefiero la primera opción, ya que encuentro que los otros a menudo me obligan a buscar códigos para las declaraciones. Definir todas las variables por adelantado también facilita la inicialización y la observación desde un depurador.
A veces declaro variables dentro de un bloque de alcance más pequeño, pero solo por una Buena Razón, de la cual tengo muy pocas. Un ejemplo podría ser después de a
fork()
, para declarar variables que solo necesita el proceso secundario. Para mí, este indicador visual es un recordatorio útil de su propósito.fuente
Como han señalado otros, GCC es permisivo en este sentido (y posiblemente en otros compiladores, dependiendo de los argumentos con los que se les llama) incluso cuando está en modo 'C89', a menos que use la verificación 'pedante'. Para ser honesto, no hay muchas buenas razones para no ser pedante; el código moderno de calidad siempre debe compilarse sin advertencias (o muy pocas cuando sabe que está haciendo algo específico que es sospechoso para el compilador como un posible error), por lo que si no puede compilar su código con una configuración pedante, probablemente necesite algo de atención.
C89 requiere que las variables se declaren antes que cualquier otra declaración dentro de cada alcance, los estándares posteriores permiten una declaración más cercana al uso (que puede ser más intuitiva y más eficiente), especialmente la declaración e inicialización simultánea de una variable de control de bucle en bucles 'for'.
fuente
Como se ha señalado, hay dos escuelas de pensamiento sobre esto.
1) Declarar todo en la parte superior de las funciones porque el año es 1987.
2) Declarar el más cercano al primer uso y en el menor alcance posible.
¡Mi respuesta a esto es HACER AMBOS! Dejame explicar:
Para funciones largas, 1) hace que la refactorización sea muy difícil. Si trabaja en una base de código donde los desarrolladores están en contra de la idea de las subrutinas, tendrá 50 declaraciones de variables al comienzo de la función y algunas de ellas podrían ser una "i" para un ciclo for que parte inferior de la función.
Por lo tanto, desarrollé declaración de TEPT a partir de esto e intenté hacer la opción 2) religiosamente.
Regresé a la opción uno por una cosa: funciones cortas. Si sus funciones son lo suficientemente cortas, tendrá pocas variables locales y, dado que la función es corta, si las coloca en la parte superior de la función, aún estarán cerca del primer uso.
Además, el antipatrón de "declarar y establecer en NULL" cuando desea declarar en la parte superior pero no ha realizado algunos cálculos necesarios para la inicialización se resuelve porque las cosas que necesita inicializar probablemente se recibirán como argumentos.
Así que ahora pienso que debería declarar en la parte superior de las funciones y lo más cerca posible del primer uso. ¡Por lo tanto! Y la forma de hacerlo es con subrutinas bien divididas.
Pero si está trabajando en una función larga, ponga las cosas más cercanas al primer uso porque de esa manera será más fácil extraer métodos.
Mi receta es esta Para todas las variables locales, tome la variable y mueva su declaración al final, compile, luego mueva la declaración justo antes del error de compilación. Ese es el primer uso. Haga esto para todas las variables locales.
Ahora, defina un bloque de alcance que comience antes de la declaración y mueva el final hasta que el programa compile
Esto no se compila porque hay más código que usa foo. Podemos notar que el compilador pudo pasar por el código que usa bar porque no usa foo. En este punto, hay dos opciones. La mecánica es simplemente mover el "}" hacia abajo hasta que se compile, y la otra opción es inspeccionar el código y determinar si el orden se puede cambiar a:
Si se puede cambiar el orden, eso es probablemente lo que desea porque acorta la vida útil de los valores temporales.
Otra cosa a tener en cuenta es que el valor de foo necesita ser preservado entre los bloques de código que lo usan, o podría ser un foo diferente en ambos. Por ejemplo
Estas situaciones necesitan más que mi procedimiento. El desarrollador tendrá que analizar el código para determinar qué hacer.
Pero el primer paso es encontrar el primer uso. Puede hacerlo visualmente, pero a veces es más fácil eliminar la declaración, intentar compilar y volver a colocarla por encima del primer uso. Si ese primer uso está dentro de una declaración if, colóquelo allí y verifique si se compila. El compilador identificará otros usos. Intente hacer un bloque de alcance que abarque ambos usos.
Una vez realizada esta parte mecánica, se hace más fácil analizar dónde están los datos. Si se usa una variable en un bloque de gran alcance, analice la situación y vea si solo está usando la misma variable para dos cosas diferentes (como una "i" que se usa para dos para bucles). Si los usos no están relacionados, cree nuevas variables para cada uno de estos usos no relacionados.
fuente
Debe declarar todas las variables en la parte superior o "localmente" en la función. La respuesta es:
Depende del tipo de sistema que esté utilizando:
1 / Sistema integrado (especialmente relacionado con vidas como Avión o Coche): le permite utilizar memoria dinámica (por ejemplo: calloc, malloc, new ...). Imagine que está trabajando en un proyecto muy grande, con 1000 ingenieros. ¿Qué sucede si asignan nueva memoria dinámica y se olvidan de eliminarla (cuando ya no se usa)? Si el sistema incrustado se ejecuta durante mucho tiempo, provocará un desbordamiento de la pila y el software se corromperá. No es fácil asegurarse de la calidad (la mejor manera es prohibir la memoria dinámica).
Si un avión se ejecuta en 30 días y no se apaga, ¿qué sucede si el software está dañado (cuando el avión todavía está en el aire)?
2 / El otro sistema como web, PC (tiene un gran espacio de memoria):
Debe declarar la variable "localmente" para optimizar la memoria utilizando. Si estos sistemas funcionan durante mucho tiempo y se produce un desbordamiento de pila (porque alguien olvidó eliminar la memoria dinámica). Simplemente haga lo simple para reiniciar la PC: P No tiene impacto en las vidas
fuente
malloc()
. Aunque nunca he visto un dispositivo que sea incapaz de hacerlo, es una buena práctica evitar la asignación dinámica en sistemas embebidos ( ver aquí ). Pero eso no tiene nada que ver con donde declaras tus variables en una función.sub rsp, 1008
se asigna espacio para toda la matriz fuera de la instrucción if. Esto es cierto paraclang
ygcc
en cada versión y nivel de optimización que probé.Citaré algunas declaraciones del manual para gcc versión 4.7.0 para una explicación clara.
"El compilador puede aceptar varios estándares básicos, como 'c90' o 'c ++ 98', y dialectos GNU de esos estándares, como 'gnu90' o 'gnu ++ 98'. Al especificar un estándar base, el compilador aceptará todos los programas que sigan ese estándar y aquellos que usen extensiones GNU que no lo contradicen. Por ejemplo, '-std = c90' desactiva ciertas características de GCC que son incompatibles con ISO C90, como las palabras clave asm y typeof, pero no otras extensiones de GNU que no tienen un significado en ISO C90, como omitir el término medio de una expresión?: ".
Creo que el punto clave de su pregunta es por qué gcc no se ajusta a C89 incluso si se usa la opción "-std = c89". No sé la versión de tu gcc, pero creo que no habrá una gran diferencia. El desarrollador de gcc nos ha dicho que la opción "-std = c89" solo significa que las extensiones que contradicen a C89 están desactivadas. Por lo tanto, no tiene nada que ver con algunas extensiones que no tienen un significado en C89. Y la extensión que no restringe la ubicación de la declaración de variable pertenece a las extensiones que no contradicen C89.
Para ser sincero, todos pensarán que debería conformar C89 totalmente a primera vista de la opción "-std = c89". Pero no lo hace. En cuanto al problema que declara que todas las variables al principio es mejor o peor es solo cuestión de costumbre.
fuente