Entiendo que si un archivo de origen necesita hacer referencia a funciones de otro archivo, entonces debe incluir su archivo de encabezado, pero no entiendo por qué el archivo de origen incluye su propio archivo de encabezado. El contenido del archivo de encabezado simplemente se copia y se pega en el archivo de origen como declaraciones de función en el tiempo de procesamiento. Para el archivo fuente que incluye su propio archivo de encabezado, tal "declaración" no me parece necesaria, de hecho, el proyecto aún compila y vincula sin problemas después de eliminar el encabezado de su archivo fuente, entonces, ¿cuál es el motivo para que el archivo fuente incluya su propia cabecera?
El principal beneficio es que el compilador verifique la consistencia de su encabezado y su implementación. Lo haces porque es conveniente, no porque es obligatorio. Definitivamente, puede ser posible hacer que el proyecto se compile y ejecute correctamente sin dicha inclusión, pero complica el mantenimiento de su proyecto a largo plazo.
Si su archivo no incluye su propio encabezado, puede encontrarse accidentalmente en una situación en la que la declaración directa de una función no coincida con la definición de la función, tal vez porque agregó o eliminó un parámetro y olvidó actualizar el encabezado. Cuando esto sucede, el código que se basa en la función que no coincide aún se compilaría, pero la llamada daría como resultado un comportamiento indefinido. Es mucho mejor que el compilador detecte este error, lo que ocurre automáticamente cuando el archivo fuente incluye su propio encabezado.
El archivo de encabezado le dice a la gente lo que puede hacer el archivo fuente.
Por lo tanto, el archivo de origen del archivo de encabezado debe conocer sus obligaciones. Por eso está incluido.
Ejemplo práctico: suponga los siguientes archivos en un proyecto:
/* foo.h */ #ifndef FOO_H #define FOO_H double foo( int x ); #endif /* foo.c */ int foo( int x ) { ... } /* main.c */ #include "foo.h" int main( void ) { double x = foo( 1 ); ... } Tenga en cuenta que la declaración en foo.h no coincide con la definición en foo.c ; los tipos de devolución son diferentes. main.c llama a la función foo asumiendo que devuelve un double , de acuerdo con la declaración en foo.h
foo.c y main.c se compilan por separado. Dado que main.c llama a foo como se declara en foo.h , se compila correctamente. Dado que foo.c no incluye foo.h , el compilador no es consciente de la falta de coincidencia de tipos entre la declaración y la definición, por lo que también compila correctamente.
Cuando vincula los dos archivos de objetos, el código de máquina para la llamada de función no coincidirá con lo que espera el código de máquina para la definición de función. La llamada a la función espera que se devuelva un valor double , pero la definición de la función devuelve un int . Esto es un problema, especialmente si los dos tipos no son del mismo tamaño. El mejor de los casos es que obtienes un resultado basura.
Al incluir foo.h en foo.c , el compilador puede detectar esta discrepancia antes de ejecutar el programa.
Y, como se señaló en una respuesta anterior, si foo.h define cualquier tipo o constante utilizada por foo.c , definitivamente debe incluirlo.