Escuché (probablemente de un maestro) que uno debe declarar todas las variables encima del programa / función, y que declarar nuevas entre las declaraciones podría causar problemas.
Pero luego estaba leyendo K&R y me encontré con esta oración: "Las declaraciones de variables (incluidas las inicializaciones) pueden seguir a la llave izquierda que introduce cualquier declaración compuesta, no solo la que comienza una función". Sigue con un ejemplo:
if (n > 0){
int i;
for (i=0;i<n;i++)
...
}
Jugué un poco con el concepto y funciona incluso con matrices. Por ejemplo:
int main(){
int x = 0 ;
while (x<10){
if (x>5){
int y[x];
y[0] = 10;
printf("%d %d\n",y[0],y[4]);
}
x++;
}
}
Entonces, ¿cuándo exactamente no puedo declarar variables? Por ejemplo, ¿qué pasa si mi declaración de variable no está justo después de la llave de apertura? Como aquí:
int main(){
int x = 10;
x++;
printf("%d\n",x);
int z = 6;
printf("%d\n",z);
}
¿Podría esto causar problemas dependiendo del programa / máquina?
fuente
gcc
es bastante laxo. Estás usando declaraciones y matrices de longitud variable c99. Compile congcc -std=c89 -pedantic
y le gritarán. Sin embargo, de acuerdo con c99, todo eso es kosher.Respuestas:
También escucho a menudo que poner variables en la parte superior de la función es la mejor manera de hacer las cosas, pero no estoy de acuerdo. Prefiero limitar las variables al ámbito más pequeño posible para que tengan menos posibilidades de ser mal utilizadas y así tenga menos cosas que llenen mi espacio mental en cada línea del programa.
Si bien todas las versiones de C permiten el alcance del bloque léxico, donde puede declarar las variables depende de la versión del estándar C a la que se dirige:
C99 en adelante o C ++
Los compiladores de C modernos, como gcc y clang, admiten los estándares C99 y C11 , que le permiten declarar una variable en cualquier lugar donde pueda ir una declaración. El alcance de la variable comienza desde el punto de la declaración hasta el final del bloque (siguiente llave de cierre).
if( x < 10 ){ printf("%d", 17); // z is not in scope in this line int z = 42; printf("%d", z); // z is in scope in this line }
También puede declarar variables internas para inicializadores de bucle. La variable solo existirá dentro del ciclo.
for(int i=0; i<10; i++){ printf("%d", i); }
ANSI C (C90)
Si está apuntando al estándar ANSI C más antiguo , entonces está limitado a declarar variables inmediatamente después de una llave de apertura 1 .
Sin embargo, esto no significa que tenga que declarar todas sus variables en la parte superior de sus funciones. En C, puede colocar un bloque delimitado por llaves en cualquier lugar donde pueda ir una declaración (no solo después de cosas como
if
ofor
) y puede usar esto para introducir nuevos alcances de variables. La siguiente es la versión ANSI C de los ejemplos anteriores de C99:if( x < 10 ){ printf("%d", 17); // z is not in scope in this line { int z = 42; printf("%d", z); // z is in scope in this line } } {int i; for(i=0; i<10; i++){ printf("%d", i); }}
1 Tenga en cuenta que si está utilizando gcc, debe pasar la
--pedantic
bandera para que realmente haga cumplir el estándar C90 y quejarse de que las variables están declaradas en el lugar incorrecto. Si solo lo usa-std=c90
, gcc acepta un superconjunto de C90 que también permite las declaraciones de variables C99 más flexibles.fuente
missingno cubre lo que permite ANSI C, pero no aborda por qué sus maestros le dijeron que declarara sus variables en la parte superior de sus funciones. Declarar variables en lugares extraños puede hacer que su código sea más difícil de leer y eso puede causar errores.
Tome el siguiente código como ejemplo.
#include <stdio.h> int main() { int i, j; i = 20; j = 30; printf("(1) i: %d, j: %d\n", i, j); { int i; i = 88; j = 99; printf("(2) i: %d, j: %d\n", i, j); } printf("(3) i: %d, j: %d\n", i, j); return 0; }
Como puede ver, lo he declarado
i
dos veces. Bueno, para ser más precisos, he declarado dos variables, ambas con el nombrei
. Podría pensar que esto causaría un error, pero no es así, porque las dosi
variables están en diferentes ámbitos. Puede ver esto con mayor claridad cuando observa el resultado de esta función.(1) i: 20, j: 30 (2) i: 88, j: 99 (3) i: 20, j: 99
Primero, asignamos 20 y 30 a
i
yj
respectivamente. Luego, dentro de las llaves, asignamos 88 y 99. Entonces, ¿por qué entoncesj
mantiene su valor, peroi
vuelve a ser 20? Es por las dosi
variables diferentes .Entre el conjunto interno de llaves, la
i
variable con el valor 20 está oculta e inaccesible, pero como no hemos declarado una nuevaj
, seguimos usando laj
del ámbito externo. Cuando dejamos el conjunto interior de llaves, lai
retención del valor 88 desaparece, y nuevamente tenemos acceso ali
con el valor 20.A veces, este comportamiento es bueno, otras veces, tal vez no, pero debe quedar claro que si usa esta característica de C indiscriminadamente, realmente puede hacer que su código sea confuso y difícil de entender.
fuente
{ int n; /* computations ... */ n = some_value; }
poder escribir{ /* computations ... */ const int n = some_value; }
.man gcc
luego busca-Wshadow
). así que estoy de acuerdo en que las variables sombreadas se demuestran aquí.Si su compilador lo permite, entonces está bien declarar en cualquier lugar que desee. De hecho, el código es más legible (en mi humilde opinión) cuando declaras la variable que usas en lugar de en la parte superior de una función porque facilita la detección de errores, por ejemplo, olvidando inicializar la variable u ocultando accidentalmente la variable.
fuente
Una publicación muestra el siguiente código:
//C99 printf("%d", 17); int z=42; printf("%d", z); //ANSI C printf("%d", 17); { int z=42; printf("%d", z); }
y creo que la implicación es que son equivalentes. Ellos no son. Si int z se coloca en la parte inferior de este fragmento de código, provoca un error de redefinición contra la primera definición z pero no contra la segunda.
Sin embargo, varias líneas de:
//C99 for(int i=0; i<10; i++){}
funciona. Mostrando la sutileza de esta regla C99.
Personalmente, rechazo con pasión esta función del C99.
El argumento de que reduce el alcance de una variable es falso, como se muestra en estos ejemplos. Bajo la nueva regla, no puede declarar una variable de manera segura hasta que haya escaneado todo el bloque, mientras que anteriormente solo necesitaba comprender lo que estaba sucediendo al principio de cada bloque.
fuente
for
es una comparación irrelevante}
. ¡Eso es! En el primer ejemplo, si desea agregar más líneas que usanz
después de printf, lo haría dentro del bloque de código, no fuera de él. Definitivamente no es necesario "escanear todo el bloque" para ver si está bien definir una nueva variable. Tengo que confesar que el primer fragmento es un ejemplo un poco artificial y tiendo a evitarlo debido a la sangría adicional que produce. Sin embargo, el{int i; for(..){ ... }}
patrón es algo que hago todo el tiempo.Según el lenguaje de programación C de K&R -
En C, todas las variables deben declararse antes de que se utilicen, generalmente al principio de la función antes de cualquier declaración ejecutable.
Aquí puedes ver la palabra que normalmente no es imprescindible.
fuente
Con clang y gcc, encontré problemas importantes con lo siguiente. gcc versión 8.2.1 20181011 clang versión 6.0.1
{ char f1[]="This_is_part1 This_is_part2"; char f2[64]; char f3[64]; sscanf(f1,"%s %s",f2,f3); //split part1 to f2, part2 to f3 }
ni al compilador le gustaba que f1, f2 o f3 estuvieran dentro del bloque. Tuve que reubicar f1, f2, f3 en el área de definición de funciones. al compilador no le importaba la definición de un número entero con el bloque.
fuente
Internamente, todas las variables locales de una función se asignan en una pila o dentro de los registros de la CPU, y luego el código de máquina generado se intercambia entre los registros y la pila (llamado derrame de registros), si el compilador es malo o si la CPU no tiene suficientes registros para Mantenga todas las bolas haciendo malabares en el aire.
Para asignar cosas en la pila, la CPU tiene dos registros especiales, uno llamado Stack Pointer (SP) y otro: Base Pointer (BP) o puntero de marco (es decir, el marco de pila local al alcance de la función actual). SP apunta dentro de la ubicación actual en una pila, mientras que BP apunta al conjunto de datos de trabajo (arriba) y los argumentos de la función (debajo). Cuando se invoca la función, empuja el BP de la función principal / llamante a la pila (señalado por SP) y establece el SP actual como el nuevo BP, luego aumenta SP por el número de bytes derramados de los registros a la pila, hace el cálculo , y al regresar, restaura el BP de su padre, sacándolo de la pila.
Generalmente, mantener sus variables dentro de las suyas
{}
alcance podría acelerar la compilación y mejorar el código generado al reducir el tamaño del gráfico que el compilador tiene que recorrer para determinar qué variables se usan, dónde y cómo. En algunos casos (especialmente cuando se trata de goto), el compilador puede perder el hecho de que la variable ya no se usará, a menos que le indique explícitamente al compilador su alcance de uso. Los compiladores podrían tener un límite de tiempo / profundidad para buscar en el gráfico del programa.El compilador podría colocar las variables declaradas cerca una de la otra en la misma área de pila, lo que significa que cargar una precargará todas las demás en la caché. De la misma manera, declarar variable
register
podría darle al compilador una pista de que desea evitar que dicha variable se derrame en la pila a toda costa.El estricto estándar C99 requiere explícito
{
declaraciones antes, mientras que las extensiones introducidas por C ++ y GCC permiten declarar vars más en el cuerpo, lo que complica las declaracionesgoto
ycase
. Además, C ++ permite declarar cosas dentro para la inicialización del ciclo, que está limitado al alcance del ciclo.Por último, pero no menos importante, para otro ser humano que lea su código, sería abrumador ver la parte superior de una función llena de medio centenar de declaraciones de variables, en lugar de ubicarlas en sus lugares de uso. También facilita comentar su uso.
TLDR: usar
{}
para establecer explícitamente el alcance de las variables puede ayudar tanto al compilador como al lector humano.fuente