¿cuál es la relación entre el archivo de objeto compartido (.so
) y el archivo de objeto (.o
)?Relación entre archivo de objeto y archivo de objeto compartido
¿me puede explicar por ejemplo?
¿cuál es la relación entre el archivo de objeto compartido (.so
) y el archivo de objeto (.o
)?Relación entre archivo de objeto y archivo de objeto compartido
¿me puede explicar por ejemplo?
A .so es análogo a un .dll en windows. A .o es exactamente lo mismo que un .obj en Visual Studio.
Digamos que usted tiene el siguiente fichero fuente C, lo llaman name.c
#include <stdio.h>
#include <stdlib.h>
void print_name(const char * name)
{
printf("My name is %s\n", name);
}
Cuando se compila, con cc name.c
se genera name.o
. El .o contiene el código compilado y los datos de todas las funciones y variables definidas en name.c, así como el índice asociado a sus nombres con el código real. Si nos fijamos en ese índice, por ejemplo con la herramienta nm
(disponible en Linux y muchos otros sistemas Unix) se dará cuenta de dos entradas:
00000000 T print_name
U printf
Lo que esto significa: hay dos símbolos (nombres de funciones o variables, pero no nombres de clases, estructuras, o cualquier tipo) almacenados en el .o. El primero, marcado con T
en realidad contiene su definición en name.o
. El otro, marcado con U
es simplemente una referencia . El código para print_name
se puede encontrar aquí, pero el código para printf
no puede. Cuando se ejecute su programa real, necesitará encontrar todos los símbolos que sean referencias y buscar sus definiciones en otros archivos de objetos para enlazarlos en un programa completo o biblioteca completa. Un archivo objeto es, por lo tanto, las definiciones encontradas en el archivo fuente, convertido a formato binario, y disponible para colocar en un programa completo.
Puede vincular archivos .o uno por uno, pero no es así: generalmente hay muchos de ellos, y son un detalle de implementación. En realidad, preferiría tenerlos todos recogidos en paquetes de objetos relacionados, con nombres bien reconocidos. Estos paquetes se llaman bibliotecas y vienen en dos formas: estáticas y dinámicas.
A biblioteca estática (en Unix) es casi siempre con el sufijo .a
(los ejemplos incluyen libc.a
que es la biblioteca C núcleo, libm.a
que es la biblioteca matemática C) y así sucesivamente. Continuando con el ejemplo, construirías tu biblioteca estática con ar rc libname.a name.o
. Si ejecuta nm
en libname.a
verá este:
name.o:
00000000 T print_name
U printf
Como se puede ver que es sobre todo una gran mesa de ficheros objeto con un índice de búsqueda de todos los nombres en él. Al igual que los archivos de objeto, contiene los símbolos definidos en cada .o
y los símbolos a los que hacen referencia. Si tuviera que vincular en otro .o (por ejemplo, date.o
a print_date
), vería otra entrada como la de arriba.
Si vincula una biblioteca estática a un archivo ejecutable, inserta toda la biblioteca en el archivo ejecutable. Esto es como vincular en todos los archivos individuales .o
. Como se puede imaginar, esto puede hacer que su programa sea muy grande, especialmente si está utilizando (como la mayoría de las aplicaciones modernas) muchas bibliotecas.
Un dinámico o biblioteca compartida tiene el sufijo .so
. Es, como su analogía estática, una gran tabla de archivos de objetos, que hace referencia a todo el código compilado. Lo construirías con cc -shared libname.so name.o
. Sin embargo, mirar con nm
es bastante diferente de la biblioteca estática. En mi sistema que contiene alrededor de dos docenas de símbolos sólo dos de las cuales son print_name
y printf
:
00001498 a _DYNAMIC
00001574 a _GLOBAL_OFFSET_TABLE_
w _Jv_RegisterClasses
00001488 d __CTOR_END__
00001484 d __CTOR_LIST__
00001490 d __DTOR_END__
0000148c d __DTOR_LIST__
00000480 r __FRAME_END__
00001494 d __JCR_END__
00001494 d __JCR_LIST__
00001590 A __bss_start
w [email protected]@GLIBC_2.1.3
00000420 t __do_global_ctors_aux
00000360 t __do_global_dtors_aux
00001588 d __dso_handle
w __gmon_start__
000003f7 t __i686.get_pc_thunk.bx
00001590 A _edata
00001594 A _end
00000454 T _fini
000002f8 T _init
00001590 b completed.5843
000003c0 t frame_dummy
0000158c d p.5841
000003fc T print_name
U [email protected]@GLIBC_2.0
una biblioteca compartida se diferencia de una biblioteca estática de una manera muy importante: no incrustarse en su ejecutable final. En cambio, el archivo ejecutable contiene una referencia a esa biblioteca compartida que se resuelve, no en tiempo de enlace, sino en tiempo de ejecución. Esto tiene una serie de ventajas:
hay algunas desventajas:
(Si se piensa en ello muchos de estos son los programas razones usan o no usan referencias y punteros en lugar de incrustar objetos directamente de una clase en otros objetos. La analogía es bastante directa.)
Ok, eso es un montón de detalles, y me he saltado un montón, como por ejemplo cómo funciona el proceso de vinculación. Espero que puedas seguirlo. Si no, pide una aclaración.
Vale la pena observar que las bibliotecas compartidas son una forma común de implementar complementos. El complemento es una forma de extender la funcionalidad de la aplicación sin cambiarla. La aplicación puede cargar una biblioteca compartida llamando a dlopen() con ruta a la biblioteca un argumento, seguido de dlsym() para encontrar un símbolo particular dentro de la biblioteca (por ejemplo, función). Que la aplicación llama a la función para realizar funciones desde la biblioteca. – dimba
Una buena introducción al mundo de la carga y la vinculación :) +1 – xtofl
Bien explicado :) +1 –