Estructura de un programa en C y flujo de compilación

Capítulo 1

Tiempo estimado de lectura: 7 minutos

+ Ejercicio

Anatomía de un archivo .c

Un programa en C suele organizarse en uno o varios archivos .c (código fuente) y, opcionalmente, archivos .h (cabeceras). Aunque el lenguaje es flexible, la mayoría de programas comparten una estructura: directivas del preprocesador, declaraciones (prototipos), definiciones de funciones y, como punto de entrada, la función main.

1) Directivas de preprocesador: #include y compañía

Las líneas que empiezan por # no son “C” propiamente dicho: las procesa el preprocesador antes de compilar. La más común es #include, que inserta el contenido de una cabecera.

  • #include <stdio.h>: busca en rutas del sistema (biblioteca estándar).
  • #include "mimodulo.h": busca primero en el directorio del proyecto (y luego en rutas del sistema).

Si falta una cabecera, el error suele aparecer en preprocesado/compilación: “file not found” o, más adelante, funciones “implícitamente declaradas” (según el estándar y los warnings).

2) La función main: punto de entrada

El sistema operativo inicia tu programa llamando a main. Formas típicas:

int main(void) { /* ... */ }
int main(int argc, char *argv[]) { /* ... */ }

La convención es devolver 0 si todo fue bien y un valor distinto de cero si hubo error.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

3) Bloques con llaves, sentencias y punto y coma

Un bloque es un conjunto de sentencias entre { y }. Muchas construcciones (funciones, if, while) introducen bloques.

Una sentencia suele terminar en ; (punto y coma). Ejemplos de sentencias: una asignación, una llamada a función, un return.

int main(void) {          /* bloque de la función */
    int x = 5;           /* sentencia (declaración con inicialización) */
    x = x + 1;           /* sentencia */
    return 0;            /* sentencia */
}

Errores típicos aquí: olvidar un ; o desbalancear llaves. El compilador suele reportar el problema “más adelante” de donde ocurrió, así que conviene revisar unas líneas antes del punto señalado.

Declaración vs definición (y por qué importa)

En C, declarar es introducir un nombre y su tipo para que el compilador sepa “qué es”. Definir es además proporcionar la implementación o reservar almacenamiento.

Ejemplos con variables

  • Declaración (sin reservar almacenamiento, normalmente en cabeceras): extern int contador;
  • Definición (reserva almacenamiento, normalmente en un .c): int contador = 0;

Si defines la misma variable global en dos .c distintos, el enlazador suele fallar con “multiple definition”. Si solo la declaras con extern pero nunca la defines, el enlazador fallará con “undefined reference”.

Ejemplos con funciones

Un prototipo es una declaración de función:

int suma(int a, int b);

La definición incluye el cuerpo:

int suma(int a, int b) {
    return a + b;
}

Si llamas a una función sin declaración visible, con opciones estrictas el compilador lo advertirá o lo tratará como error. Si la declaras pero no la defines en ningún objeto enlazado, el error aparecerá en el enlazado (“undefined reference”).

Pipeline de construcción: de .c a ejecutable

Cuando ejecutas gcc o clang para generar un programa, internamente ocurren etapas. Entenderlas te ayuda a ubicar el origen de los errores.

EtapaEntradaSalidaErrores típicos
Preprocesado.c + cabecerasC expandidofatal error: ... file not found, macros mal formadas
CompilaciónC preprocesadoEnsamblador (.s) o intermedioerrores de sintaxis, tipos incompatibles, variables no declaradas
Ensamblado.sObjeto (.o)raro en C básico; problemas de toolchain
Enlazado.o + libreríasEjecutableundefined reference, multiple definition, librerías faltantes

Cómo ver el resultado de cada etapa

Con gcc/clang puedes detenerte en etapas:

  • Solo preprocesar (útil para diagnosticar #include): gcc -E main.c
  • Compilar a objeto (sin enlazar): gcc -c main.c -o main.o
  • Generar ensamblador: gcc -S main.c -o main.s

Ejemplo mínimo que compila

Archivo main.c:

#include <stdio.h>

int main(void) {
    puts("Hola, C");
    return 0;
}

Compilación recomendada con warnings (GCC o Clang):

gcc -std=c11 -Wall -Wextra -Wpedantic -O0 -g main.c -o programa

Notas prácticas:

  • -std=c11: fija el estándar (evita comportamientos “por defecto” del compilador).
  • -Wall -Wextra -Wpedantic: activa advertencias útiles; trátalas como señales de problemas reales.
  • -O0 -g: ideal para aprender y depurar (sin optimizaciones, con símbolos de depuración).

Ejemplo con varios archivos: prototipos, objetos y enlazado

Supón dos archivos: uno con la función y otro con main.

Archivo util.h (declaración)

#ifndef UTIL_H
#define UTIL_H

int suma(int a, int b);

#endif

Archivo util.c (definición)

#include "util.h"

int suma(int a, int b) {
    return a + b;
}

Archivo main.c (usa la función)

#include <stdio.h>
#include "util.h"

int main(void) {
    printf("%d\n", suma(2, 3));
    return 0;
}

Compilar paso a paso

gcc -std=c11 -Wall -Wextra -Wpedantic -c util.c -o util.o
gcc -std=c11 -Wall -Wextra -Wpedantic -c main.c -o main.o
gcc util.o main.o -o programa

O en una sola línea (el compilador hace el pipeline por ti):

gcc -std=c11 -Wall -Wextra -Wpedantic util.c main.c -o programa

Errores comunes y en qué etapa ocurren

1) #include faltante: “file not found”

Ejemplo de mensaje:

main.c:1:10: fatal error: util.h: No such file or directory

Cómo corregirlo de forma sistemática:

  • Verifica el nombre exacto del archivo (mayúsculas/minúsculas importan en Linux/macOS).
  • Si es cabecera del proyecto, usa comillas: #include "util.h".
  • Asegura que el archivo esté en el directorio correcto o añade ruta de inclusión: gcc -Iinclude ... (si tus cabeceras están en include/).

2) Símbolo no resuelto en enlazado: “undefined reference”

Ejemplo típico:

/usr/bin/ld: main.o: in function `main':
main.c:(.text+0x15): undefined reference to `suma'
collect2: error: ld returned 1 exit status

Interpretación:

  • main.o llama a suma, pero el enlazador no encuentra su definición en los objetos/librerías enlazados.

Corrección paso a paso:

  • Confirma que suma está definida en algún .c (no solo declarada en un .h).
  • Asegúrate de compilar y enlazar ese archivo: incluye util.c en el comando o enlaza util.o.
  • Verifica coincidencia exacta de firma (nombre y parámetros). Si cambiaste el prototipo en util.h, recompila todo.

3) Definición múltiple: “multiple definition”

Suele ocurrir cuando pones una definición global en una cabecera incluida por varios .c.

/usr/bin/ld: util.o:(.bss+0x0): multiple definition of `contador'; main.o:(.bss+0x0): first defined here

Corrección:

  • En la cabecera: declara con extern (sin definir).
  • En un solo .c: define la variable.
/* en util.h */
extern int contador;

/* en util.c */
int contador = 0;

Cómo leer mensajes del compilador sin “probar al azar”

Regla 1: identifica la etapa por el tipo de mensaje

  • fatal error: ... file not found” suele ser preprocesado.
  • error: expected ...” o “undeclared” suele ser compilación.
  • undefined reference” o “ld:” suele ser enlazado.

Regla 2: empieza por el primer error real

Un error de sintaxis temprano puede generar una cascada de errores secundarios. Corrige el primero que aparece en la salida (el más arriba) y recompila.

Regla 3: usa la ubicación (archivo:línea:columna)

Los compiladores suelen indicar archivo:línea:columna. Ve exactamente a esa posición y revisa también 5–10 líneas antes (por ejemplo, un ; faltante suele “romper” la línea siguiente).

Regla 4: convierte warnings en tareas concretas

Con -Wall -Wextra -Wpedantic verás advertencias como:

warning: unused variable 'x' [-Wunused-variable]

No lo ignores: decide si debes usar la variable, eliminarla o marcarla explícitamente (por ejemplo, en parámetros no usados). Mantener el proyecto sin warnings reduce errores reales.

Regla 5: reproduce el problema con el comando exacto

Evita cambiar muchas cosas a la vez. Mantén un comando estable (por ejemplo, el de compilación recomendado) y modifica una sola causa probable. Si usas varios archivos, recompila los que cambiaste y luego enlaza; si dudas, recompila todo para evitar objetos desactualizados.

Comandos típicos (GCC/Clang) recomendados

Compilar un archivo

clang -std=c11 -Wall -Wextra -Wpedantic -O0 -g main.c -o programa

Compilar varios archivos

gcc -std=c11 -Wall -Wextra -Wpedantic -O0 -g main.c util.c -o programa

Solo compilar a objetos (sin enlazar)

gcc -std=c11 -Wall -Wextra -Wpedantic -O0 -g -c main.c util.c

Ver el preprocesado para diagnosticar #include

gcc -E main.c > main.i

El archivo main.i resultante te permite comprobar si una cabecera se está insertando como esperas y qué macros están afectando al código.

Ahora responde el ejercicio sobre el contenido:

Si al compilar un proyecto con varios archivos aparece el error de enlazado "undefined reference" a una función llamada desde main, ¿cuál es la causa más probable?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

“Undefined reference” ocurre en el enlazado: hay una llamada a una función, pero el enlazador no encuentra su definición en los archivos objeto o librerías enlazadas. Suele solucionarse compilando y enlazando el .c que la implementa.

Siguiente capítulo

Tipos de datos en C, variables y constantes

Arrow Right Icon
Portada de libro electrónico gratuitaFundamentos de programación en C desde cero: variables, control de flujo y funciones
9%

Fundamentos de programación en C desde cero: variables, control de flujo y funciones

Nuevo curso

11 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.