¿Cómo funcionan los archivos de encabezado y fuente en C?

8 minutos de lectura

avatar de usuario
Dan Lugg

He examinado detenidamente los posibles duplicados, sin embargo, ninguna de las respuestas se está hundiendo.

tl; dr: ¿Cómo se relacionan los archivos fuente y de encabezado en C? ¿Los proyectos resuelven las dependencias de declaración/definición implícitamente en el momento de la compilación?

Estoy tratando de entender cómo el compilador entiende La relación entre .c y .h archivos

Teniendo en cuenta estos archivos:

encabezado.h:

int returnSeven(void);

fuente.c:

int returnSeven(void){
    return 7;
}

C Principal:

#include <stdio.h>
#include <stdlib.h>
#include "header.h"
int main(void){
    printf("%d", returnSeven());
    return 0;
}

¿Compilará este lío? Actualmente estoy haciendo mi trabajo en NetBeans 7.0 con CCG de Cygwin, que automatiza gran parte de la tarea de compilación. Cuando se compila un proyecto, ¿los archivos del proyecto involucrados resolverán esta inclusión implícita de source.c sobre la base de las declaraciones en header.h?

  • Sí, esto se compilará (¿y por qué crees que es un “desastre”?). Los conceptos a aprender son unidades de compilación y enlace.

    – Jesper

    5 mayo 2011 a las 21:56

  • Gracias Jesper; Jaja, no es un lío, supongo que es mejor reservar esa palabra para describir mi cerebro, leyendo entre 3 niveles de principiante C libros. ciertamente investigaré unidades de compilación y enlacesin embargo, en aras de centrarnos en el aprendizaje de la sintaxis, dejaré NetBeans + CCG resolver esto para mí. Dado que, cada vez que un archivo de encabezado dado tiene declaraciones para las cuales existen definiciones en otras partes del proyecto, la inclusión de ese archivo de encabezado es suficiente para brindar acceso a la funcionalidad definida, y el compilador resolverá los detalles.

    – Dan Lug

    5 mayo 2011 a las 22:00


  • header.h las necesidades incluyen guardias;)

    – alternativa

    5 mayo 2011 a las 22:01

  • También recomiendo compilar esto a mano. gcc main.c -c -o main.o, gcc source.c -c -o source.o, gcc main.o source.o -o program compilará eso. Facilita ver las unidades compiladas por separado y el enlace al final.

    – alternativa

    5 mayo 2011 a las 22:02

La conversión de archivos de código fuente C a un programa ejecutable normalmente se realiza en dos pasos: compilando y enlace.

Primero, el compilador convierte el código fuente en archivos objeto (*.o). Luego, el enlazador toma estos archivos de objetos, junto con bibliotecas enlazadas estáticamente y crea un programa ejecutable.

En el primer paso, el compilador toma una unidad de compilaciónque normalmente es un archivo fuente preprocesado (es decir, un archivo fuente con el contenido de todos los encabezados que contiene). #includes) y lo convierte en un archivo de objeto.

En cada unidad de compilación, todas las funciones que se utilizan deben estar declarado, para que el compilador sepa que la función existe y cuáles son sus argumentos. En tu ejemplo, la declaración de la función returnSeven está en el archivo de cabecera header.h. cuando compilas main.cincluye el encabezado con la declaración para que el compilador sepa que returnSeven existe cuando se compila main.c.

Cuando el enlazador hace su trabajo, necesita encontrar el definición de cada función. Cada función debe definirse exactamente una vez en uno de los archivos de objetos; si hay varios archivos de objetos que contienen la definición de la misma función, el enlazador se detendrá con un error.

tu funcion returnSeven se define en source.c (y el main la función se define en main.c).

Entonces, para resumir, tienes dos unidades de compilación: source.c y main.c (con los archivos de cabecera que incluye). Compile estos en dos archivos de objetos: source.o y main.o. El primero contendrá la definición de returnSevenel segundo la definición de main. Luego, el enlazador pegará esos dos juntos en un programa ejecutable.

Acerca de la vinculación:

Hay enlace externo y enlace interno. De forma predeterminada, las funciones tienen enlaces externos, lo que significa que el compilador hace que estas funciones sean visibles para el enlazador. Si haces una función static, tiene enlace interno: solo es visible dentro de la unidad de compilación en la que está definido (el enlazador no sabrá que existe). Esto puede ser útil para funciones que hacen algo internamente en un archivo fuente y que desea ocultar del resto del programa.

avatar de usuario
Oliver Charlesworth

El lenguaje C no tiene concepto de archivos fuente y archivos de encabezado (y tampoco el compilador). Esto es simplemente una convención; recuerde que un archivo de encabezado siempre es #included en un archivo fuente; el preprocesador literalmente solo copia y pega el contenido, antes de que comience la compilación adecuada.

tu ejemplo deberían compilar (a pesar de los errores de sintaxis tontos). Usando GCC, por ejemplo, primero podría hacer:

gcc -c -o source.o source.c
gcc -c -o main.o main.c

Esto compila cada archivo fuente por separado, creando archivos de objetos independientes. En este punto, returnSeven() no se ha resuelto por dentro main.c; el compilador simplemente ha marcado el archivo de objeto de una manera que indica que debe resolverse en el futuro. Entonces, en esta etapa, no es un problema que main.c no puedo ver un definición de returnSeven(). (Nota: esto es distinto del hecho de que main.c debe ser capaz de ver un declaración de returnSeven() para compilar; debe saber que de hecho es una función, y cuál es su prototipo. Por eso debes #include "source.h" en main.c.)

Luego haces:

gcc -o my_prog source.o main.o

Esta Enlaces los dos archivos de objeto juntos en un binario ejecutable y realiza la resolución de símbolos. En nuestro ejemplo, esto es posible, porque main.o requiere returnSeven()y esto es expuesto por source.o. En los casos en que todo no coincida, se producirá un error del enlazador.

  • (Nota: esto es distinto del hecho de que main.c debe poder ver una declaración de returnSeven() : estoy siendo pedante, pero esto no es del todo correcto. El compilador felizmente compilará (con una advertencia en C99) esto código, y el enlazador lo resuelve, por lo general con malos efectos, por ejemplo, en el archivo ac, llamar x=bob(1,2,3,4) y en el archivo bc, void bob(char *a){} compilará, vinculará y ejecutará.

    – Mattnz

    5 de mayo de 2011 a las 23:49


  • Absolutamente respuesta de clase mundial. Me encantan los ejemplos minimalistas de instrucciones del compilador GCC

    – Decano P

    18 de julio de 2017 a las 13:58

No hay nada mágico en la compilación. ¡Ni automático!

Los archivos de encabezado básicamente brindan información al compilador, casi nunca codifican.
Esa información por sí sola no suele ser suficiente para crear un programa completo.

Considere el programa “hola mundo” (con el programa más simple puts función):

#include <stdio.h>
int main(void) {
    puts("Hello, World!");
    return 0;
}

sin el encabezado, el compilador no sabe cómo lidiar con puts() (no es una palabra clave de C). El encabezado le permite al compilador saber cómo administrar los argumentos y devolver el valor.

Sin embargo, cómo funciona la función no se especifica en ninguna parte de este código simple. Alguien más ha escrito el código para puts() e incluyó el código compilado en una biblioteca. El código de esa biblioteca se incluye con el código compilado de su código fuente como parte del proceso de compilación.

Ahora considere que quería su propia versión de puts()

int main(void) {
    myputs("Hello, World!");
    return 0;
}

Compilar solo este código genera un error porque el compilador no tiene información sobre la función. Puedes proporcionar esa información.

int myputs(const char *line);
int main(void) {
    myputs("Hello, World!");
    return 0;
}

y el código ahora compila — pero no enlaza, es decir, no produce un ejecutable, porque no hay código para myputs(). Así que escribes el código para myputs() en un archivo llamado “myputs.c”

#include <stdio.h>
int myputs(const char *line) {
    while (*line) putchar(*line++);
    return 0;
}

y tienes que recordar compilar ambas cosas su primer archivo fuente y “myputs.c” juntos.

Después de un tiempo, su archivo “myputs.c” se ha expandido a un puñado de funciones y necesita incluir la información sobre todas las funciones (sus prototipos) en los archivos fuente que desea usar.
Es más conveniente escribir todos los prototipos en un solo archivo y #include ese archivo Con la inclusión no corres el riesgo de cometer un error al escribir el prototipo.

Sin embargo, todavía tiene que compilar y vincular todos los archivos de código.


Cuando crecen aún más, metes todo el código ya compilado en una biblioteca… y esa es otra historia 🙂

Los archivos de encabezado se utilizan para separar las declaraciones de interfaz que corresponden a las implementaciones en los archivos fuente. Se abusa de ellos de otras maneras, pero este es el caso común. Esto no es para el compilador, es para los humanos que escriben el código.

La mayoría de los compiladores en realidad no ven los dos archivos por separado, el preprocesador los combina.

El compilador en sí no tiene un “conocimiento” específico de las relaciones entre los archivos fuente y los archivos de encabezado. Esos tipos de relaciones suelen definirse mediante archivos de proyecto (por ejemplo, archivo MAKE, solución, etc.).

El ejemplo dado parece como si se compilara correctamente. Necesitará compilar ambos archivos fuente y luego el enlazador necesitará ambos archivos objeto para producir el ejecutable.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad