Para entender cómo se organiza un proyecto de C que consta de múltiples archivos, debemos distinguir dos conceptos que suelen confundirse: la vinculación (linkage), que determina qué nombres son visibles para el enlazador (linker), y la duración de almacenamiento (storage duration), que define cuánto tiempo vive una variable en memoria.
El especificador static es un “camaleón” en C: su comportamiento cambia radicalmente según dónde lo apliques. Si lo usas en una variable o función a nivel de archivo, le asignas vinculación interna; esto significa que el símbolo queda oculto para cualquier otra unidad de traducción (archivo .c), actuando como un mecanismo de encapsulación similar al private de otros lenguajes. Por el contrario, si lo usas dentro de una función, no cambias su ámbito (scope), sino su duración: la variable se almacena en la sección de datos del programa y su valor persiste entre llamadas a la función.
Por su parte, extern es la herramienta para la comunicación entre módulos. Se utiliza para declarar que una variable o función existe, pero que su definición (la reserva de memoria) se encuentra en otro lugar. Un error común es confundir una declaración con una definición. Una declaración extern int x; no ocupa espacio en la memoria; simplemente le dice al compilador: “Confía en mí, el enlazador encontrará a x más adelante”.
Debes usar static para cualquier función o variable que sea un detalle de implementación y no deba formar parte de la API pública de tu módulo. Usa extern cuando necesites compartir un estado global o una constante entre archivos. Si te equivocas con static, el enlazador te dará un error de “símbolo no encontrado” (undefined reference) al intentar usarlo en otro archivo. Si te equivocas con extern (por ejemplo, definiendo la variable en un .h en lugar de declararla), el enlazador lanzará un error de “definición múltiple” (multiple definition).
#include <stdio.h>
/* --- Módulo de Configuración (Simulado) --- */
// Definición de una variable con vinculación externa.
// Es visible para todo el programa.
int sistema_estado = 0;
// Declaración de la variable anterior usando 'extern'.
// En un proyecto real, esta línea iría en un archivo .h.
// Esto le dice al compilador que 'sistema_estado' ya existe en algún lugar.
extern int sistema_estado;
// Constante a nivel de archivo.
// En C, las constantes tienen vinculación externa por defecto.
const int VERSION_API = 2;
/* --- Módulo de Utilidades (Simulado) --- */
// Variable con vinculación interna.
// Ningún otro archivo puede ver o modificar 'contador_privado'.
static int contador_privado = 0;
// Prototipo de función con vinculación interna.
// Se usa para funciones de soporte que no deben ser parte de la API.
static void log_interno(const char *mensaje);
// Función con vinculación externa (por defecto).
// Es la interfaz pública del módulo.
void ejecutar_operacion(int incremento) {
// Variable local con duración estática.
// Se inicializa una única vez al cargar el programa.
// Su valor persiste entre llamadas a 'ejecutar_operacion'.
static int llamadas_totales = 0;
contador_privado += incremento;
sistema_estado += incremento;
llamadas_totales++;
char buffer[50];
// Usamos un formateador simple para evitar dependencias complejas
snprintf(buffer, sizeof(buffer), "Operacion #%d", llamadas_totales);
log_interno(buffer);
}
void mostrar_resumen() {
printf("--- Resumen del Sistema ---\n");
printf("API Version: %d\n", VERSION_API);
printf("Estado Global: %d\n", sistema_estado);
// Si intentaras acceder a 'contador_privado' desde un archivo externo,
// el compilador daría un error de "símbolo no encontrado".
printf("Estado Interno (solo visible aquí): %d\n", contador_privado);
printf("---------------------------\n");
}
static void log_interno(const char *mensaje) {
// Esta función es 'privada' al archivo de traducción.
printf("[LOG INTERNO]: %s\n", mensaje);
}
int main(void) {
ejecutar_operacion(10);
ejecutar_operacion(5);
mostrar_resumen();
return 0;
}
Análisis del código
Fíjate en cómo gestionamos la visibilidad. La variable sistema_estado se define sin modificadores, lo que le otorga vinculación externa. La línea extern int sistema_estado; es una declaración que refuerza este hecho; aunque en un solo archivo es redundante, en un entorno de múltiples archivos es la única forma de permitir que un main.c vea la variable definida en un config.c.
La función log_interno y la variable contador_privado están marcadas con static. Esto es fundamental para el encapsulamiento. Si intentáramos llamar a log_interno desde main si este estuviera en otro archivo, el enlazador fallaría porque static ha restringido su ámbito a la unidad de traducción actual.
En ejecutar_operacion, la variable llamadas_totales es una variable local, pero al declararla como static, su duración de almacenamiento cambia. No se crea en el stack cada vez que la función se invoca; en su lugar, se asigna en el segmento de datos. Por eso, el valor de llamadas_totales se mantiene y aumenta entre la primera y la segunda llamada de main.
El error frecuente
Uno de los errores más persistentes al escalar proyectos es definir variables en los archivos de cabecera (.h).
Código incorrecto (globals.h):
// ERROR: Esto es una definición, no una declaración. int puntuacion_jugador = 0;
Si incluyes globals.h en jugador.c y en enemigo.c, el compilador generará dos archivos objeto que contienen la misma variable puntuacion_jugador. Al intentar enlazarlos, el linker encontrará dos definiciones para el mismo símbolo y lanzará un error de “multiple definition”.
La solución correcta:
En el .h solo declaras con extern:
// CORRECTO: Solo una declaración. extern int puntuacion_jugador;
Y en un único archivo .c (por ejemplo globals.c), realizas la definición real:
// CORRECTO: Única definición. int puntuacion_jugador = 0;
N° 22