Por qué su código no se compila: el misterio de extern en C++
\n\n¿Alguna vez ha escrito un programa ordenado en C++, lo ha dividido en varios archivos, lo ha compilado y ha recibido un muro de errores como multiple definition of 'myVariable' o undefined reference to 'myVariable'? ¿Le suena familiar? Si es así, no está solo. La pregunta "How do I use extern to share variables between source files?" se encuentra entre las más populares en Stack Overflow con decenas de miles de visitas.
El problema es simple: cuando un proyecto crece, es imposible mantener todo el código en un solo main.cpp. Pero, ¿cómo hacer que una variable declarada en config.cpp sea visible en game.cpp y render.cpp? La intuición dice: "¡Solo declárala globalmente!" — pero el compilador piensa diferente. Aquí es donde entra la pequeña pero poderosa palabra clave extern. Analicemos cómo funciona en la práctica, para que nunca más se confunda con el enlazado.
¿Qué es extern y para qué sirve?
\n\nEn C++, cada nombre (variable, función, clase) debe ser declarado (declared) antes de usarse y definido (defined) exactamente una vez. La definición es lo que asigna memoria. La declaración es simplemente una promesa al compilador: "No te preocupes, la definición está en otro archivo, solo te muestro el tipo y el nombre".
\n\nLa palabra clave extern es exactamente esa promesa. Le dice al compilador: "Esta variable está definida en otro lugar, no asignes memoria aquí — solo dame acceso a ella".
Sin extern, cada intento de declarar una variable global en dos archivos .cpp provocará un error del enlazador: dos definiciones — dos espacios de memoria — conflicto.
La regla de los tres archivos: cómo organizar el código
\n\nEl patrón estándar para compartir variables entre archivos en C++ es el siguiente:
\n\n- \n
- Archivo de definición (*.cpp) — contiene una definición de la variable. \n
- Archivo de encabezado (*.h) — contiene la declaración
externde la variable. \n - Cualquier otro archivo .cpp — incluye el encabezado y usa la variable. \n
Ejemplo 1: Compartiendo un número entero
\n\nImagine que estamos escribiendo un juego y queremos almacenar una puntuación global en un archivo separado. Así es como se hace correctamente.
\n\nArchivo: globals.cpp — aquí vive la variable real:
// globals.cpp\n#include "globals.h"\n\nint gameScore = 0; // Definición: la memoria se asigna aquí\n\nArchivo: globals.h — declaración para todos los demás:
// globals.h\n#ifndef GLOBALS_H\n#define GLOBALS_H\n\nextern int gameScore; // Declaración: "busca la definición en otro .cpp"\n\n#endif // GLOBALS_H\n\nArchivo: main.cpp — usamos la variable:
// main.cpp\n#include <iostream>\n#include "globals.h"\n\nvoid updateScore(int points) {\n gameScore += points; // ¡Funciona! extern dio acceso\n}\n\nint main() {\n gameScore = 100;\n updateScore(50);\n std::cout << "Score: " << gameScore << std::endl; // Mostrará 150\n return 0;\n}\n\nArchivo: display.cpp — otro archivo que necesita acceso:
// display.cpp\n#include <iostream>\n#include "globals.h"\n\nvoid showScore() {\n std::cout << "Current score: " << gameScore << std::endl;\n}\n\nObserve: gameScore está definido exactamente una vez — en globals.cpp. En todos los demás lugares usamos extern int gameScore (a través del encabezado).
Error común n.º 1: definición en el archivo de encabezado
\n\nLos principiantes suelen escribir esto en el archivo .h:
// MAL: ba