Archivos de encabezado C y compilación/vinculación

8 minutos de lectura

Archivos de encabezado C y compilacionvinculacion
Arístides

Sé que los archivos de encabezado tienen declaraciones hacia adelante de varias funciones, estructuras, etc. que se usan en el .c archivo que ‘llama’ al #include, ¿Correcto? Según tengo entendido, la “separación de poderes” se produce así:

Archivo de cabecera: func.h

  • contiene declaración directa de función

    int func(int i);
    

Archivo fuente C: func.c

  • contiene la definición de la función real

    #include "func.h"
    
    int func(int i) {
        return ++i ;
    }
    

archivo fuente C source.c (el programa “real”):

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

int main(void) {
    int res = func(3);
    printf("%i", res);
}

Mi pregunta es: viendo que el #include es simplemente una directiva del compilador que copia el contenido del .h en el expediente que #include está adentro, ¿cómo funciona el .c file sabe cómo ejecutar realmente la función? Todo lo que obtiene es el int func(int i);, entonces, ¿cómo puede realmente realizar la función? ¿Cómo accede a la definición real de func? ¿El encabezado incluye algún tipo de ‘puntero’ que dice “esa es mi definición, allí!”?

¿Como funciona?

  • Esa es la magia de Linker resolviendo las definiciones y asegurándose de que las cosas que afirmó que existían durante la compilación realmente existen.

    – Uchia Itachi

    31 de agosto de 2013 a las 12:35

  • Al tratar con archivos de encabezado, es posible que desee leer sobre incluir guardias.

    – Un tipo programador

    31 de agosto de 2013 a las 12:36

  • Sé acerca de incluir guardias (ifndef y todo eso) pero los omití por brevedad.

    – Arístides

    31 de agosto de 2013 a las 12:42

  • A mí me parece que estás pensando que el código fuente se está utilizando para ejecutar el programa, como en los lenguajes de secuencias de comandos (JavaScript, etc.). Ese no es el caso. El código fuente de C se transforma primero (por el compilador y el enlazador) en código de máquina, que luego es ejecutado directamente por el hardware del procesador.

    – zentrunix

    31 de agosto de 2013 a las 13:10

  • ¿Qué quiere decir con “el código fuente se está utilizando para ejecutar el programa”? C (o la implementación de GCC) se compila, no se interpreta. Sé que está compilado en código de máquina antes de tiempo. Es el código fuente que se utiliza para generar el código máquina. No estoy seguro de cuál es tu punto.

    – Arístides

    31 de agosto de 2013 a las 13:20


Uchia Itachi dio la respuesta. Es el enlazador.

Usando el compilador GNU C gcc compilarías un programa de un archivo como

gcc hello.c -o hello # generating the executable hello

Pero al compilar el programa de dos (o más) archivos como se describe en su ejemplo, tendría que hacer lo siguiente:

gcc -c func.c # generates the object file func.o
gcc -c main.c # generates the object file main.o
gcc func.o main.o -o main # generates the executable main

Cada archivo de objeto tiene símbolos externos (puede pensar en él como miembros públicos). Las funciones son por defecto externas mientras que las variables (globales) son por defecto internas. Puede cambiar este comportamiento definiendo

static int func(int i) { # static linkage
    return ++i ;
}

o

/* global variable accessible from other modules (object files) */
extern int global_variable = 10; 

Cuando encuentra una llamada a una función, no definida en el módulo principal, el enlazador busca todos los archivos de objeto (y bibliotecas) proporcionados como entrada para el módulo donde se define la función llamada. De forma predeterminada, probablemente tenga algunas bibliotecas vinculadas a su programa, así es como puede usar printfya está compilado en una biblioteca.

Si está realmente interesado, intente algo de programación de ensamblaje. Estos nombres son el equivalente de etiquetas en código ensamblador.

  • Entonces, con GCC, el patrón es: 1. Use el indicador -c con cada .c (con definiciones) y .h (con prototipos de función) para crear cada .o 2. Use el indicador -o y cada archivo .o para crear el archivo final exe ?

    – Arístides

    31 de agosto de 2013 a las 14:05

  • Si. la opción “-c” es para “compilar”, solo para compilar el código objeto en archivos de objetos. gcc sin -c reconoce que las entradas son archivos de objetos, por lo que simplemente los vincula mediante el enlazador. Y finalmente, el indicador -o es opcional, se usa para especificar el nombre del archivo de salida del ejecutable.

    –Emil Vatai

    31 de agosto de 2013 a las 14:34

Es el enlazador que maneja todo eso. El compilador simplemente emite una secuencia especial en el archivo de objeto que dice “Tengo este símbolo externo funcresuélvalo” para el enlazador. Luego, el enlazador ve eso y busca el símbolo en todos los demás archivos de objetos y bibliotecas.

  • ¿Significa esto todo .c ¿Se buscará el archivo en el proyecto?

    –Lidong Guo

    31 de agosto de 2013 a las 12:50

  • @LidongGuo Si compila todos los archivos de origen en la línea de comando, o si crea archivos de objetos de todas las fuentes y los vincula, entonces sí, se buscarán. Sin embargo, no se hace automáticamente, debe decirle al enlazador qué archivos de objeto desea vincular, y solo se buscarán esos.

    – Un tipo programador

    31 de agosto de 2013 a las 12:55

  • @Someprogrammerdude, pero ¿cómo sabe el enlazador del archivo .o qué funciones se exportan bajo qué encabezado y qué funciones no están resueltas bajo qué encabezado? ¿Es el nombre del archivo de encabezado o un hash de algún tipo almacenado en la tabla de símbolos para que el enlazador se asegure de que coincide (es decir, que el encabezado está en ambos archivos y solo se exporta un archivo de objeto)

    –Lewis Kelsey

    4 de abril de 2019 a las 9:47

  • Los archivos de encabezado @LewisKelsey no juegan ningún papel en la vinculación. Un archivo de objeto es, básicamente, un único unidad de traducción, en forma compilada. El enlazador sabe dónde se define un símbolo porque los archivos de objetos también contienen los símbolos definidos en la unidad de traducción. Si desea obtener más detalles sobre cómo funcionan los enlazadores, le sugiero que busque información al respecto en su motor de búsqueda favorito, ya que es un tema demasiado amplio para responder aquí (especialmente en los comentarios).

    – Un tipo programador

    4 de abril de 2019 a las 9:54

Una declaración de un símbolo sin una definición dentro de la misma unidad de compilación le dice al compilador que compile con un marcador de posición para la dirección de ese símbolo en un archivo de objeto.

El enlazador verá que se requiere una definición para el símbolo y buscará definiciones externas del símbolo en bibliotecas y otros archivos de objetos.

Si el vinculador encuentra una definición, el marcador de posición en el archivo de objeto original se reemplazará con la dirección encontrada en el ejecutable final.

El encabezado brinda acceso no solo a otros .c archivos en el mismo programa, pero también a bibliotecas que pueden distribuirse en forma binaria. la relacion de uno .c archivo a otro es exactamente lo mismo que una biblioteca que depende de otra.

Dado que una interfaz de programación debe estar en forma de texto sin importar el formato de la implementación, los archivos de encabezado tienen sentido como una separación de preocupaciones.

Como han mencionado otros, el programa que resuelve las llamadas a funciones y los accesos entre bibliotecas y fuentes (unidades de traducción) se denomina enlazador.

El enlazador no funciona con encabezados. Simplemente crea una gran tabla de todos los nombres que están definidos en todas las unidades de traducción y bibliotecas, luego vincula esos nombres a las líneas de código que acceden a ellos. El uso arcaico de C incluso permite llamar a una función sin ninguna declaración de implementación; simplemente se asumió que cada tipo indefinido era un int.

Generalmente cuando compilas un archivo como este:

gcc -o program program.c

Realmente está llamando a un programa controlador, que hace lo siguiente:

  • preprocesamiento (si solicitó que fuera un paso separado) usando cpp.
  • compilar (puede estar integrado con el preprocesamiento) usando cc1
  • montaje, uso as (gas, el ensamblador GNU).
  • enlazando usando collect2que también utiliza ld (el enlazador GNU).

Por lo general, durante las primeras 3 etapas, crea un archivo de objeto simple (.o extensión), que se crea al compilar una unidad de compilación (es decir, un archivo .c, con #include y otras directivas reemplazadas por el preprocesador).

La cuarta etapa es la que crea el ejecutable final. Después de la compilación de una unidad, el compilador marca varios fragmentos de código como referencias que el enlazador debe resolver. El trabajo del enlazador es buscar entre muchas unidades de compilación y resolver referencias a unidades de compilación externas.

¿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