- GCC es una colección de compiladores que sigue un flujo claro: preprocesado, compilación, ensamblado y enlace, controlable con opciones como -c, -S y -E.
- Las extensiones de archivo (.c, .cpp, .o, .so, .a) y opciones como -o, -I, -L y -l determinan cómo se generan ejecutables y cómo se integran cabeceras y bibliotecas.
- Opciones como -Wall, -Werror, -g, -v y -D permiten mejorar la calidad del código, depurar con comodidad y adaptar la compilación sin tocar los fuentes.
- Mediante -save-temps, la sintaxis @archivo y la separación de compilación y enlace, GCC se integra fácilmente en proyectos grandes y flujos de construcción avanzados.
Si programas en C o C++ en GNU/Linux, tarde o temprano acabas topándote con el comando gcc y todo su ecosistema de opciones, fases y archivos. Al principio puede parecer un monstruo con mil interruptores, pero en realidad sigue unas reglas bastante lógicas. Conociendo bien cómo funciona, compilar deja de ser “magia negra” y se convierte en algo que controlas tú.
En este artículo vamos a repasar con calma cómo usar gcc desde cero, qué hace en cada fase de compilación, qué tipos de archivos genera y cuáles son las opciones más prácticas en el día a día: desde compilar un único fichero hasta trabajar con proyectos grandes, bibliotecas y avisos del compilador. La idea es que, cuando termines de leer, puedas mirar cualquier comando gcc relativamente complejo y entender qué está haciendo sin despeinarte.
Qué es GCC y qué lenguajes soporta
GCC son las siglas de GNU Compiler Collection, es decir, la colección de compiladores de GNU. Aunque lo más habitual es asociarlo al lenguaje C, en realidad es un conjunto de herramientas capaz de manejar varios lenguajes: C, C++, Fortran, Ada, Objective-C, e incluso otros, dependiendo de cómo esté construido el paquete de tu distribución.
En casi cualquier distribución GNU/Linux encontrarás gcc instalado o disponible en los repositorios, ya que es una pieza clave para compilar el propio sistema y un montón de paquetes. Normalmente lo invocas desde la línea de comandos con el programa gcc para C y g++ para C++, aunque todo forma parte de la misma familia de herramientas.
Si quieres comprobar qué versión de GCC tienes, basta con ejecutar en una terminal:
gcc --version
Ese comando no compila nada, simplemente muestra la versión del compilador, información de compilación y el objetivo para el que fue construido (por ejemplo, i686-linux-gnu o x86_64-linux-gnu). Además, si necesitas más detalles de cómo se ha configurado, puedes verlos en salidas verbosas con la opción -v, que muestra datos de configuración interna, rutas y herramientas auxiliares utilizadas.
Como cualquier herramienta estándar de GNU/Linux, gcc tiene su propia página de manual. Puedes consultarla con:
man gcc
Ahí verás la lista completa de opciones, muchas más de las que se suelen usar a diario. El manual es largo y denso, pero es una referencia imprescindible cuando quieres saber qué hace exactamente un interruptor concreto.

Sintaxis básica del comando gcc
La llamada típica a gcc tiene una forma muy sencilla: opciones seguidas de nombres de archivo. Una sinopsis genérica sería algo así:
gcc archivos...
Por debajo, gcc distingue entre parámetros que son opciones (conmutadores que empiezan con -) y parámetros que son archivos. Todo lo que no reconoce como opción lo considera un archivo, y según su extensión decidirá si se trata de código fuente, código objeto u otra cosa.
Las opciones “estilo UNIX” suelen ser cortas y empiezan con un guion simple, por ejemplo -Wall o -c. Algunas de estas opciones cortas se pueden agrupar, aunque en gcc es más común verlas separadas. Las opciones “estilo GNU” son más largas y comienzan con doble guion, como --version; normalmente se indican de una en una.
Muchas opciones aceptan un parámetro adicional, como un número, un directorio, un nombre de archivo o una cadena. Por ejemplo, -o nombre sirve para indicar el archivo de salida, -I ruta para añadir directorios de cabeceras, o -L ruta para rutas de bibliotecas. Es importante respetar espacios y sintaxis exacta (en -I y -L no debe haber espacio entre la opción y la ruta).
El orden de los argumentos suele dar bastante igual cuando hablamos de archivos fuente y opciones generales, pero en el caso de las bibliotecas enlazadas (-l) el orden sí importa, porque el enlazador las resuelve de izquierda a derecha. En la mayoría de proyectos pequeños no lo notarás, pero cuando empiezas a jugar con muchas librerías puede darte quebraderos de cabeza.
Extensiones de archivo y su significado para GCC
Una de las primeras cosas que hace gcc al procesar su línea de comandos es mirar la extensión de cada archivo para decidir qué hacer con él. No es lo mismo un .c que un .o, o un .s. Estas son las extensiones más habituales y cómo las interpreta:
- .c: archivo de código fuente en C. Normalmente se pasa por todas las fases: preprocesado, compilación, ensamblado y, si toca, enlazado.
- .C, .cc, .cpp, .c++, .cp, .cxx: archivos de código fuente en C++. Lo habitual hoy en día es usar
.cpppara mantener una convención clara. - .m: archivo de código fuente en Objective-C. Al compilarlo es necesario enlazar con la biblioteca correspondiente (por ejemplo,
libobjc.aen ciertos entornos) para que el programa funcione correctamente. - .i: resultado del preprocesado de un archivo C. Es código C “plano”, con las directivas de preprocesador ya resueltas (
#include, macros, etc.). - .ii: análogo a
.ipero para C++. Es el código C++ después del paso de preprocesador. - .s: código fuente en ensamblador. Si gcc recibe este tipo de archivo, normalmente lo pasará a ensamblador/ligador según la fase que hayas pedido.
- .o: archivo de código objeto, es decir, el resultado de compilar/ensamblar pero sin enlazar todavía. Se puede combinar con otros
.opara construir el ejecutable final. - .h: archivo de cabecera para el preprocesador. Suele contener declaraciones, prototipos y definiciones inline. No se pasa directamente en la línea de comandos de gcc; se incluye desde los fuentes con
#include. - Cualquier otra extensión o token que no sea una opción válida suele tratarse como archivo de entrada genérico, normalmente asumido como código objeto o archivo que el enlazador debe procesar.
Además de estas extensiones básicas, en sistemas GNU/Linux es habitual encontrarse con bibliotecas estáticas con extensión .a y bibliotecas compartidas con extensión .so. Las primeras son colecciones de objetos para enlace estático (no muy recomendables hoy en día salvo casos muy concretos), y las segundas son bibliotecas dinámicas cargadas en tiempo de ejecución.

Fases del proceso de compilación con GCC
Aunque desde fuera parezca que gcc “lo hace todo de una tacada”, internamente el proceso de construcción tiene varias fases bien definidas. Cuando lanzas un comando típico, suele encadenar estos pasos en este orden:
- Preprocesado: se resuelven las directivas
#include, macros, condicionales de compilación (#if,#ifdef, etc.). El resultado es un archivo de texto donde todas esas sustituciones ya se han realizado. - Compilación: el código preprocesado se traduce a un lenguaje intermedio y finalmente a código ensamblador para la arquitectura objetivo.
- Ensamblado: el ensamblador convierte ese código en un archivo de código objeto (
.o), que contiene instrucciones de máquina y símbolos sin resolver. - Enlazado: el enlazador toma uno o varios archivos objeto y bibliotecas, resuelve las referencias entre ellos y genera el archivo ejecutable (o una biblioteca compartida).
Las tres primeras fases trabajan sobre cada archivo fuente de manera independiente y como resultado generan un archivo .o por fuente. La fase de enlace se encarga de unir todos esos objetos y bibliotecas externas para producir el binario final.
GCC permite lanzar cada fase por separado mediante opciones específicas que detienen el proceso después de cierto punto. Esto es muy útil en proyectos grandes, donde sueles compilar muchos archivos por separado y luego enlazarlos todos juntos, a menudo usando sistemas de construcción como make (ver crear y dominar Makefile).
Conviene pensar siempre que “compilar” y “enlazar” son acciones distintas, incluso aunque lances un único comando que haga ambas. En muchos entornos profesionales, la compilación se hace de forma incremental (solo lo que ha cambiado) y el enlazado se realiza al final, lo que ayuda a ahorrar tiempo y a organizar mejor el código.
De un fichero de código a un ejecutable sencillo
Para aterrizar un poco todo esto, imagina que tienes en tu directorio de usuario una carpeta llamada Lab2 con un archivo fuente en C llamado main_es.c. Ese archivo contiene una función main (donde empieza la ejecución del programa) y alguna función auxiliar más.
Si quieres obtener un ejecutable sin complicarte, puedes lanzar directamente:
gcc main_es.c
Tras ejecutar este comando, en el directorio actual aparecerá un archivo llamado a.out, que es el nombre de ejecutable por defecto de gcc cuando no se especifica salida. En sistemas tipo UNIX es toda una tradición, aunque en proyectos reales casi siempre vas a querer cambiarlo por algo más descriptivo.
Para ejecutar este archivo desde tu carpeta de usuario, como no está en ninguna ruta del PATH del sistema, tendrás que escribir:
./a.out
El prefijo ./ le indica al sistema que busque el ejecutable en el directorio actual. Si no lo pusieras, el intérprete de comandos solo intentaría localizarlo en las rutas predefinidas del sistema, donde no está tu programa recién compilado.
En cuanto empieces a generar más binarios, verás que trabajar siempre con a.out no es nada cómodo, así que la primera opción imprescindible que hay que aprender es la que permite nombrar el ejecutable a tu gusto.
Elegir el nombre del ejecutable con -o
La opción -o sirve para indicar a gcc el nombre del archivo de salida que quieres generar. Puedes usarla tanto para ejecutables como para archivos intermedios (objetos, ensamblador, etc.), dependiendo de la fase a la que cortes el proceso.
Siguiendo con el ejemplo anterior, si quieres que el binario se llame main_es en lugar de a.out, ejecutarías:
gcc -o main_es main_es.c
Después de este comando comprobarás que en tu carpeta tienes al menos un ejecutable con el nombre que has elegido, y posiblemente también a.out si lo habías generado antes. En ese caso, puedes borrar el que no te interese para no llenar el directorio de binarios antiguos.
La sintaxis general es siempre la misma: -o nombre_salida seguido de los archivos de entrada. Si estás solo compilando sin enlazar (por ejemplo, con -c), -o te permitirá escoger el nombre del objeto resultante.

Ejemplos básicos de compilación con gcc y g++
A partir de aquí, los ejemplos típicos suelen ser variaciones de la misma idea: compilar uno o varios ficheros fuente y decidir qué tipo de salida queremos. Algunos casos sencillos serían los siguientes:
Para compilar un programa en C llamado hola.c y obtener el ejecutable por defecto a.out:
gcc hola.c
Si prefieres que el ejecutable se llame hola, bastaría con:
gcc hola.c -o hola
Para un programa escrito en C++ en el archivo hola.cpp lo normal es usar g++, que es el “frontend” de GCC para C++. Un ejemplo básico sería:
g++ hola.cpp -o hola
Cuando solo quieres generar el código objeto (.o) de un archivo fuente, sin producir todavía el ejecutable, utilizas la opción -c, que compila y ensambla pero no enlaza. Por ejemplo, para C y también para C++:
gcc -c hola.c
g++ -c hola.cpp
En ambos casos, terminarás con archivos hola.o que después podrás enlazar juntos con otros objetos o bibliotecas. Este enfoque es el que se usa en proyectos medianos y grandes, donde cada módulo se compila de forma independiente.
Si quieres que el ejecutable se genere en un directorio concreto, como por ejemplo bin dentro de tu carpeta de usuario, puedes indicar una ruta relativa o absoluta en la opción -o:
gcc hola.c -o ~/bin/hola
De esta manera, tras compilar, el binario quedará colocado directamente en el directorio deseado, listo para añadirse al PATH o ejecutarse desde ahí.
Controlando cada fase: -c, -S, -E y -save-temps
Como hemos visto, gcc permite detener el proceso en distintas etapas para inspeccionar o reutilizar los resultados intermedios. Las opciones más usadas para esto son:
- -c: compila y ensambla, pero no enlaza. El resultado es un archivo objeto
.opor cada archivo fuente. Es ideal cuando se trabaja con varios módulos que luego se enlazan todos juntos. - -S: compila, pero no ensambla. Genera código ensamblador en un archivo con extensión
.s. Resulta útil para estudiar el código máquina que produce el compilador. - -E: ejecuta solo el preprocesador y muestra la salida por pantalla (normalmente se redirige a un archivo
.io.ii). Es perfecto para analizar cómo se expanden las macros y los#include. - -save-temps: hace que gcc guarde todos los archivos temporales intermedios generados durante la compilación: el resultado del preprocesador, el ensamblador y el objeto. Así puedes revisarlo todo junto.
Por ejemplo, si quieres ver solo el resultado del preprocesador de main.c, puedes ejecutar:
gcc -E main.c > main.i
Si lo que te interesa es el ensamblador producido, usarías:
gcc -S main.c > main.s
Para obtener únicamente el objeto, sin enlazar todavía, basta con:
gcc -c main.c
Y si quieres que gcc te deje todos los archivos intermedios además del ejecutable, puedes hacer:
gcc -save-temps main.c
Al listar después el directorio verás algo como a.out main.c main.i main.o main.s, con todos los pasos disponibles para inspección o depuración.
Gestionar avisos y errores: -Wall, -Werror, -v y -g
Una parte crucial del trabajo con gcc es aprender a aprovechar los mensajes de advertencia y los informes detallados, en lugar de ignorarlos. Varias opciones nos ayudan a sacarles partido.
La opción -Wall activa la mayoría de las advertencias importantes del compilador. No son literalmente “todas las advertencias habidas y por haber”, pero sí un conjunto bastante completo que te permite detectar código potencialmente problemático: variables sin inicializar, conversiones sospechosas, funciones sin prototipo, etc.
Por ejemplo, supón este código en C:
#include <stdio.h>
int main(void) {
int i;
printf("\n Hello world \n", i);
return 0;
}
Al compilarlo con:
gcc -Wall main.c -o main
gcc te avisará de que la variable i se usa sin haber sido inicializada, lo que puede provocar comportamientos inesperados. Sin -Wall, muchos de estos avisos pasarían desapercibidos y acabarías persiguiendo bugs extraños.
Si además de ver las advertencias quieres tratar cualquier aviso como si fuese un error, puedes usar la opción -Werror, que convierte las advertencias en errores de compilación. Esto obliga a mantener el código limpio, sobre todo en proyectos donde se quiere un nivel de calidad alto.
Imagina este fragmento de código en C:
#include <stdio.h>
int main(void) {
char c;
// Uso de c sin inicializar
printf("\n Las cosas geek \n", c);
return 0;
}
Compilando con:
gcc -Wall -Werror main.c -o main
el compilador interrumpirá el proceso, ya que la advertencia sobre la variable sin inicializar se tratará como un error. Esto puede parecer estricto, pero evita que los problemas se cuelen en el binario final.
La opción -v activa un modo detallado (“verbose”) en el que gcc muestra todos los comandos internos que va ejecutando en cada fase, así como información precisa de rutas, versiones y programas auxiliares que invoca. Es especialmente útil cuando algo falla en la fase de enlace o en el uso de bibliotecas, y quieres ver qué está pasando exactamente.
Por último, la opción -g inserta en el ejecutable la información necesaria para depurar el programa con herramientas como GDB. Al compilar con -g se mantiene la relación entre líneas de código fuente y direcciones de memoria en el binario, facilitando la inspección de variables, pilas de llamadas y puntos de ruptura.
Incluir cabeceras y bibliotecas: -I, -L y -l
En cuanto tu programa crece un poco, lo normal es empezar a usar tus propias cabeceras y bibliotecas externas, además de las estándar. Para ello, gcc ofrece varias opciones destinadas a ajustar las rutas de búsqueda.
La opción -I sirve para añadir directorios donde el preprocesador buscará archivos de cabecera .h. La sintaxis es importante: no debe haber espacio entre -I y la ruta. Un ejemplo sería:
gcc -I/usr/include/mis_headers main.c -o main
Si tu código tiene un #include "mi_header.h", el compilador buscará primero en el directorio del archivo fuente, después en las rutas indicadas con -I y finalmente en los directorios estándar del sistema. Puedes repetir -I tantas veces como quieras para añadir varios directorios de búsqueda.
Por otra parte, la opción -L indica directorios adicionales donde el enlazador debe buscar bibliotecas al resolver símbolos externos. También aquí no se deja espacio entre la opción y la ruta, por ejemplo:
gcc main.c -L/usr/lib/mis_libs -lmi_biblioteca -o main
En este caso, cuando vea -lmi_biblioteca, el enlazador intentará encontrar archivos como libmi_biblioteca.so o libmi_biblioteca.a dentro de los directorios estándar y también en /usr/lib/mis_libs. De nuevo, puedes repetir -L para añadir múltiples ubicaciones.
El orden en el que pones los .o y las opciones -l suele ser relevante: las bibliotecas deberían ir al final de la línea de comandos, después de los objetos que las necesitan, para que el enlazador pueda resolver correctamente las dependencias. En muchos ejemplos sencillos esto pasa desapercibido, pero es buena costumbre acostumbrarse a ese orden desde el principio.
Definir macros desde la línea de comandos con -D
Además de las macros definidas en tu código con #define, gcc permite establecer macros directamente desde la línea de comandos usando la opción -D. Esto es muy útil para compilar el mismo código con pequeñas variaciones de comportamiento sin tocar los fuentes.
La forma básica es:
gcc -DMY_MACRO main.c -o main
Con esto, en cualquier parte de tu código donde compruebes #ifdef MY_MACRO, esa condición se considerará verdadera durante la compilación. También puedes asignar valores:
gcc -DMY_MACRO=10 main.c -o main
Esto te permite, por ejemplo, activar trazas de depuración, cambiar constantes de configuración o compilar versiones “de pruebas” y “de producción” de forma cómoda, simplemente cambiando las macros definidas en la línea de comandos.
Pasar un listado de opciones con la sintaxis @archivo
En casos donde el comando gcc se vuelve muy largo, lleno de rutas, macros, banderas de optimización y demás, puede ser incómodo de mantener a mano. Para estas situaciones, gcc permite leer opciones desde un archivo usando la sintaxis @archivo.
La idea es sencilla: creas un fichero de texto, por ejemplo opt_file, en el que escribes todas las opciones separadas por espacios o saltos de línea, tal y como las pondrías en la línea de comandos:
-Wall -Werror -Iinclude -Llib -lmi_biblioteca -DMODO_DEBUG
Después, al compilar, puedes hacer algo como:
gcc @opt_file main.c -o main
GCC interpretará el contenido de opt_file como si hubieras escrito esas opciones directamente, simplificando muchísimo el comando y facilitando su mantenimiento. Es una alternativa light a usar un Makefile cuando todavía no quieres meterte en herramientas más complejas.
GCC como colección de herramientas: compilador, enlazador y más
Aunque solemos hablar de gcc como si fuese un único programa gigante que lo hace todo, en realidad actúa como una especie de orquestador de varias herramientas especializadas. Según el tipo de archivo y las opciones que le pases, va invocando internamente al preprocesador, al compilador de cada lenguaje, al ensamblador y al enlazador.
Si pasas un archivo .c, por ejemplo, gcc invoca al compilador de C; si le pasas un .cpp o usas g++, llamará al compilador de C++. Con el modo detallado -v puedes ver qué comandos exactos lanza en cada fase, lo que ayuda a entender la cadena de herramientas que hay por detrás.
En proyectos más grandes es habitual separar claramente “compilación” y “enlace” como tareas distintas. Sueles ver sistemas de compilación que ejecutan gcc por cada archivo fuente para generar los objetos, y luego hacen una llamada final para enlazarlos todos. Esta forma de trabajar reduce el tiempo de compilación incremental y se integra perfecto con herramientas como make, ninja u otras.
Además, a partir de los mismos objetos y bibliotecas puedes crear ejecutables normales, bibliotecas estáticas (.a) o bibliotecas compartidas (.so). Para bibliotecas compartidas en C, verás a menudo comandos del estilo:
gcc -c -Wall -Werror -fPIC Cfile.cgcc -shared -o libCfile.so Cfile.o
Aquí -fPIC genera código independiente de posición (requisito típico para bibliotecas compartidas), y -shared indica que queremos una .so en lugar de un ejecutable normal. Esa biblioteca compartida luego se enlaza desde otros programas con -lCfile y el correspondiente -L para la ruta.
Cuando empiezas a trabajar con varios archivos fuente, bibliotecas y sistemas de construcción, términos como “Makefile”, “enlace estático/dinámico” o “objetos compartidos” dejan de sonar lejanos: simplemente son diferentes formas de decirle a gcc y compañía cómo combinar tus piezas de código en programas funcionales.
Al final, dominar el comando gcc es cuestión de ir perdiendo el miedo a sus opciones y acostumbrarte a pensar en archivos fuente, objetos, cabeceras y bibliotecas como partes de un mismo engranaje. Una vez pillas la lógica, se vuelve mucho más fácil leer comandos de compilación complejos, detectar qué falta cuando hay errores de enlace o ajustar la compilación a las necesidades de tu proyecto sin tener que recurrir siempre a plantillas ajenas.
Redactor apasionado del mundo de los bytes y la tecnología en general. Me encanta compartir mis conocimientos a través de la escritura, y eso es lo que haré en este blog, mostrarte todo lo más interesante sobre gadgets, software, hardware, tendencias tecnológicas, y más. Mi objetivo es ayudarte a navegar por el mundo digital de forma sencilla y entretenida.