Conceptos clave: alcance, duración y visibilidad
En C, una variable no solo se define por su tipo y nombre: también importa dónde se puede usar (alcance), cuánto tiempo existe (duración) y desde dónde se puede referenciar (visibilidad o enlace). Distinguir estos conceptos evita errores difíciles de detectar, especialmente cuando el mismo identificador aparece en varios lugares.
- Alcance (scope): región del código donde el nombre de la variable es válido. Normalmente depende de llaves
{ }(bloques) o del nivel de archivo. - Duración (lifetime / storage duration): cuánto tiempo “vive” la variable durante la ejecución. Puede ser automática (entra/sale con el bloque) o estática (vive todo el programa).
- Visibilidad (linkage): si un identificador puede ser referenciado desde otros archivos (
.c) o solo dentro del archivo actual. Esto se controla principalmente constatica nivel de archivo.
| Tipo típico | Ejemplo | Alcance | Duración | Visibilidad |
|---|---|---|---|---|
| Local automática | int x; dentro de una función | Bloque | Automática | Solo dentro de la función/bloque |
| Local estática | static int c; dentro de una función | Bloque | Estática | Solo dentro de la función |
| Global | int g; fuera de funciones | Archivo (desde la declaración) | Estática | Por defecto externa (otros archivos pueden verla con extern) |
| Global “privada” | static int g; fuera de funciones | Archivo | Estática | Solo dentro del archivo |
Variables locales: alcance de bloque y duración automática
Una variable declarada dentro de una función (y sin static) tiene alcance de bloque y duración automática. Esto significa que existe desde que la ejecución entra al bloque hasta que sale de él.
Ejemplo: modificar dentro de un bloque anidado sí afecta fuera (misma variable)
Si no redeclaras la variable, un bloque interno puede modificar la misma variable definida en el bloque externo.
int main(void) { int x = 10; { x = 20; /* modifica la misma x */ } /* aquí x vale 20 */ return 0;}Ejemplo: modificar dentro de un bloque NO afecta fuera (shadowing)
Si en el bloque interno declaras otra variable con el mismo nombre, la variable interna sombras (shadow) a la externa. El resultado típico: crees que modificas la externa, pero en realidad cambias una distinta.
int main(void) { int x = 10; { int x = 20; /* nueva x: sombrea a la externa */ x = 30; /* modifica la x interna */ } /* aquí x sigue valiendo 10 */ return 0;}Este patrón genera bugs sutiles porque el código “parece” correcto a simple vista. En especial, ocurre en bloques de if, for o al introducir variables temporales con nombres genéricos como i, temp o result.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
Guía práctica: cómo detectar y evitar shadowing
Paso 1: identifica el bloque donde se declaró la variable
Ubica la declaración más cercana hacia arriba. En C, el nombre se resuelve buscando en el bloque actual; si no está, se busca en el bloque contenedor, y así sucesivamente.
Paso 2: revisa si hay una redeclaración con el mismo nombre
Si encuentras una declaración del mismo identificador en un bloque interno, la externa queda inaccesible dentro de ese bloque.
Paso 3: aplica una corrección según la intención
- Si querías modificar la variable externa: no redeclares el nombre en el bloque interno.
- Si querías una variable temporal: usa un nombre distinto (por ejemplo,
x_temp), o limita su uso a una función auxiliar.
Paso 4: apóyate en advertencias del compilador
Activa advertencias para que el compilador te avise de sombras. Por ejemplo, en GCC/Clang suele ayudar -Wall -Wextra -Wshadow. Esto no cambia el comportamiento del programa, pero reduce errores.
Variables globales: alcance de archivo y visibilidad entre archivos
Una variable declarada fuera de cualquier función tiene alcance de archivo (desde su declaración hasta el final del archivo) y duración estática (existe durante toda la ejecución). La diferencia importante es su visibilidad:
- Sin
statica nivel de archivo: la variable tiene, por defecto, visibilidad externa (otros archivos pueden referenciarla medianteextern). - Con
statica nivel de archivo: la variable queda “privada” del archivo (solo se puede usar en ese.c).
Ejemplo: global accesible desde el mismo archivo
int contador_global = 0; /* visible en este archivo desde aquí */void inc(void) { contador_global++;}Ejemplo: global “privada” del archivo con static
static int contador_archivo = 0; /* solo visible en este .c */void inc(void) { contador_archivo++;}Usar static a nivel de archivo es una técnica común para evitar colisiones de nombres y reducir acoplamiento: otros módulos no pueden depender accidentalmente de esa variable.
static dentro de una función: misma visibilidad, distinta duración
Cuando declaras una variable local con static, su alcance sigue siendo el bloque donde se declara (solo se puede usar allí), pero su duración pasa a ser estática: conserva su valor entre llamadas.
Ejemplo: contador persistente entre llamadas
int siguiente_id(void) { static int id = 0; /* se inicializa una vez */ id++; return id;}Aquí, id no “se reinicia” cada vez que se llama a la función. Esto es útil para contadores, cachés simples o estados internos. También puede ser peligroso si introduces estado oculto que complica pruebas y reutilización.
Ejemplos comparativos: “no afecta fuera” y “sí afecta fuera”
Caso A: no afecta fuera (variable distinta por shadowing)
void demo_shadowing(void) { int total = 100; if (total > 0) { int total = 0; /* sombrea */ total += 50; /* cambia el total interno */ } /* total sigue siendo 100 */}Caso B: sí afecta fuera (misma variable, sin redeclarar)
void demo_mismo_identificador(void) { int total = 100; if (total > 0) { total += 50; /* cambia el total externo */ } /* total ahora es 150 */}Caso C: “viceversa” con global vs local (la local no afecta a la global)
Si existe una global y dentro de una función declaras una local con el mismo nombre, la local sombrea a la global dentro de la función. Esto suele ser fuente de errores porque parece que estás actualizando el estado global, pero no.
int estado = 0;void set_estado(void) { int estado = 1; /* sombrea a la global */ estado = 2; /* modifica la local */ /* la global sigue en 0 */}Si realmente necesitas modificar la global, no la sombres: usa otro nombre para la local o elimina la redeclaración.
Recomendaciones de estilo: menos globales, dependencias explícitas
Minimiza variables globales
- Prefiere pasar datos como parámetros y devolver resultados (o usar punteros a estructuras) en lugar de depender de globales.
- Si necesitas estado compartido dentro de un módulo, prefiere
statica nivel de archivo para encapsularlo. - Evita nombres genéricos en globales (
data,value,flag) porque aumentan el riesgo de colisiones y confusión.
Haz dependencias explícitas en las funciones
Una función es más fácil de probar y reutilizar si su comportamiento depende de sus parámetros, no de variables externas ocultas.
- En lugar de:
void procesar(void)que usa una globalconfig, prefiere:void procesar(const Config* config). - Si necesitas “estado” entre llamadas, considera agruparlo en una estructura y pasar un puntero:
void step(Estado* e), en vez de usarstaticlocal salvo que sea realmente un detalle interno.
Evita shadowing deliberado
- No reutilices el mismo nombre para variables en bloques anidados.
- Usa nombres que expresen intención:
indice,limite,acumulado,acumulado_parcial. - Activa advertencias del compilador para detectar sombras y trátalas como errores en tu flujo de trabajo.