Con una frecuencia depresiva (me sucedió otra vez justo antes de escribir esto), la visión que tienen muchos programadores sobre el proceso de pasar de código fuente a un ejecutable estáticamente enlazado en un lenguaje compilado es:
El paso 3 es, por supuesto, el paso de enlazado. ¿Por qué diría una cosa tan atroz? He estado en soporte técnico por décadas, y tengo las siguientes preguntas una y otra vez:
Seguido de “¿ahora qué hago?”, usualmente mezclado junto con las frases “parece que” y “de alguna manera”, y un aura de total desconcierto. Son los “parece que” y “de alguna manera” los que indican que el proceso de enlace es visto como un proceso mágico, presumiblemente entendible sólo por magos y brujos. El proceso de compilado no provoca este tipo de frases, implicando que los programadores generalmente entienden cómo funcionan los compiladores o, al menos, qué hacen.
Un linker es un programa muy estúpido, ordinario y directo. Todo lo que hace es concatenar el código y las secciones de datos de los archivos objeto, conectar las referencias a los símbolos con sus definiciones, empujar símbolos sin resolver fuera de la biblioteca y escribir un ejecutable. Eso es todo. ¡Sin hechizos! ¡Sin magia! Lo tedioso de escribir un linker es usualmente decodificar y generar formatos de archivo ridículamente complicados, pero eso no cambia la esencia natural de un linker.
Digamos que el linker está diciendo “def está definido más de una vez”. Muchos lenguajes de programación, tales como C, C++, y D, tienen ambos, declaraciones y definiciones. Las declaraciones normalmente van en archivos de encabezados, tales como:
extern int iii;
lo cual genera una referencia externa al símbolo iii
. Una definición,
por otro lado, en realidad establece el almacenamiento para el símbolo,
usualmente aparece en el archivo de implementación, y luce así:
int iii = 3;
¿Cuántas definiciones puede haber por cada símbolo? Como en la película
Highlander, sólo puede haber una. Así que, ¿qué tal si una definición de
iii
aparece en más de un archivo de implementación?
// Archivo a.c
int iii = 3;
// Archivo b.c
double iii(int x) { return 3.7; }
El linker se quejará porque iii
está siendo definido varias veces.
No sólo puede haber uno, debe haber uno. Si iii
sólo aparece como una
declaración, pero nunca en una definición, el linkers se quejará sobre
iii
por ser un símbolo no resuelto.
Para determinar por qué un ejecutable es del tamaño que es, dale un vistazo al archivo de mapa que los linkers generan opcionalmente. Un archivo de mapa es una lista de todos los símbolos en el ejecutable junto con sus direcciones. Te dice qué módulos fueron enlazados desde la biblioteca y el tamaño de cada módulo. Ahora puedes ver de dónde viene tanta hinchazón. Frecuentemente habrá módulos de biblioteca que no tendrás idea por qué fueron enlazados. Para saberlo, quita temporalmente el módulo sospechoso de la biblioteca y vuelve a enlazar. El error de “símbolo no definido” generado indicará quién está referenciando ese módulo.
Aunque no siempre es obvio por qué aparece un mensaje del linker en particular, no hay nada mágico sobre los linkers. La mecánica es directa: son los detalles lo que tienes que averiguar en cada caso.
Traducción: Espartaco Palma