Esta es probablemente una respuesta más detallada de lo que quería, pero creo que una explicación decente está justificada.
En C y C ++, un archivo fuente se define como una unidad de traducción . Por convención, los archivos de encabezado contienen declaraciones de funciones, definiciones de tipo y definiciones de clase. Las implementaciones de funciones reales residen en unidades de traducción, es decir, archivos .cpp.
La idea detrás de esto es que las funciones y las funciones miembro de clase / estructura se compilan y ensamblan una vez, luego otras funciones pueden llamar a ese código desde un lugar sin hacer duplicados. Sus funciones se declaran como "externas" implícitamente.
/* Function declaration, usually found in headers. */
/* Implicitly 'extern', i.e the symbol is visible everywhere, not just locally.*/
int add(int, int);
/* function body, or function definition. */
int add(int a, int b)
{
return a + b;
}
Si desea que una función sea local para una unidad de traducción, debe definirla como 'estática'. ¿Qué significa esto? Significa que si incluye archivos de origen con funciones externas, obtendrá errores de redefinición, porque el compilador se encuentra con la misma implementación más de una vez. Por lo tanto, desea que todas sus unidades de traducción vean la declaración de la función pero no el cuerpo de la función .
Entonces, ¿cómo se mezcla todo al final? Ese es el trabajo del enlazador. Un vinculador lee todos los archivos de objetos generados por la etapa de ensamblador y resuelve los símbolos. Como dije antes, un símbolo es solo un nombre. Por ejemplo, el nombre de una variable o una función. Cuando las unidades de traducción que llaman funciones o declaran tipos no conocen la implementación de esas funciones o tipos, se dice que esos símbolos no están resueltos. El enlazador resuelve el símbolo no resuelto conectando la unidad de traducción que contiene el símbolo indefinido junto con el que contiene la implementación. Uf. Esto es cierto para todos los símbolos visibles desde el exterior, ya sea que estén implementados en su código o proporcionados por una biblioteca adicional. Una biblioteca es realmente solo un archivo con código reutilizable.
Hay dos excepciones notables. Primero, si tiene una función pequeña, puede hacerla en línea. Esto significa que el código de máquina generado no genera una llamada de función externa, sino que literalmente se concatena in situ. Como generalmente son pequeños, el tamaño de la sobrecarga no importa. Puedes imaginar que sean estáticos en la forma en que funcionan. Por lo tanto, es seguro implementar funciones en línea en los encabezados. Las implementaciones de funciones dentro de una definición de clase o estructura también son incorporadas automáticamente por el compilador.
La otra excepción son las plantillas. Dado que el compilador necesita ver la definición completa del tipo de plantilla al instanciarlos, no es posible desacoplar la implementación de la definición como con funciones independientes o clases normales. Bueno, tal vez esto sea posible ahora, pero obtener un amplio soporte del compilador para la palabra clave "exportar" tomó mucho, mucho tiempo. Entonces, sin soporte para 'exportar', las unidades de traducción obtienen sus propias copias locales de tipos y funciones con plantilla instanciadas, de forma similar a cómo funcionan las funciones en línea. Con soporte para 'exportar', este no es el caso.
Para las dos excepciones, algunas personas encuentran "más agradable" poner las implementaciones de funciones en línea, funciones con plantillas y tipos con plantillas en archivos .cpp y luego #incluir el archivo .cpp. Si esto es un encabezado o un archivo fuente realmente no importa; Al preprocesador no le importa y es solo una convención.
Un resumen rápido de todo el proceso desde el código C ++ (varios archivos) hasta un ejecutable final:
- Se ejecuta el preprocesador , que analiza todas las directivas que comienzan con un '#'. La directiva #include concatena el archivo incluido con inferior, por ejemplo. También hace macro-reemplazo y token-pegado.
- El compilador real se ejecuta en el archivo de texto intermedio después de la etapa de preprocesador y emite el código del ensamblador.
- El ensamblador se ejecuta en el archivo de ensamblaje y emite código de máquina, generalmente se denomina archivo de objeto y sigue el formato ejecutable binario del sistema operativo en cuestión. Por ejemplo, Windows usa el PE (formato ejecutable portátil), mientras que Linux usa el formato Unix System V ELF, con extensiones GNU. En esta etapa, los símbolos todavía están marcados como indefinidos.
- Finalmente, se ejecuta el enlazador . Todas las etapas anteriores se ejecutaron en cada unidad de traducción en orden. Sin embargo, la etapa de enlace funciona en todos los archivos de objetos generados que fueron generados por el ensamblador. El enlazador resuelve símbolos y hace mucha magia, como crear secciones y segmentos, que depende de la plataforma de destino y el formato binario. Los programadores no están obligados a saber esto en general, pero seguramente ayuda en algunos casos.
Nuevamente, esto fue definitivamente más de lo que pediste, pero espero que los detalles esenciales te ayuden a ver la imagen más grande.