Una de las reglas y mejores prácticas más importantes al escribir una biblioteca es colocar todos los símbolos de la biblioteca en un espacio de nombres específico de la biblioteca. C ++ facilita esto gracias a la namespace
palabra clave. En C, el enfoque habitual es prefijar los identificadores con algún prefijo específico de la biblioteca.
Reglas de la norma C ponen algunas restricciones en los que (para la compilación de seguridad): compilador de CA puede mirar sólo los primeros 8 caracteres de un identificador, por lo que foobar2k_eggs
y foobar2k_spam
puede interpretarse como los mismos identificadores válidamente - sin embargo cada compilador moderna permite identificadores largos arbitrarias , por lo que en nuestros tiempos (el siglo XXI) no deberíamos tener que preocuparnos por esto.
Pero, ¿qué sucede si se enfrenta a algunas bibliotecas cuyos nombres / idenfiers de símbolos no puede cambiar? Tal vez solo tenga un binario estático y los encabezados o no quiera, o no se le permita ajustar y recompilar usted mismo.
Respuestas:
Al menos en el caso de las bibliotecas estáticas , puede solucionarlo con bastante comodidad.
Considere esos encabezados de bibliotecas foo y bar . Por el bien de este tutorial, también te daré los archivos fuente.
ejemplos / ex01 / foo.h
int spam(void); double eggs(void);
examples / ex01 / foo.c (esto puede ser opaco / no disponible)
int the_spams; double the_eggs; int spam() { return the_spams++; } double eggs() { return the_eggs--; }
ejemplo / ex01 / bar.h
int spam(int new_spams); double eggs(double new_eggs);
examples / ex01 / bar.c (esto puede ser opaco / no disponible)
int the_spams; double the_eggs; int spam(int new_spams) { int old_spams = the_spams; the_spams = new_spams; return old_spams; } double eggs(double new_eggs) { double old_eggs = the_eggs; the_eggs = new_eggs; return old_eggs; }
Queremos usarlos en un programa foobar
ejemplo / ex01 / foobar.c
#include <stdio.h> #include "foo.h" #include "bar.h" int main() { const int new_bar_spam = 3; const double new_bar_eggs = 5.0f; printf("foo: spam = %d, eggs = %f\n", spam(), eggs() ); printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n", spam(new_bar_spam), new_bar_spam, eggs(new_bar_eggs), new_bar_eggs ); return 0; }
Un problema se hace evidente de inmediato: C no conoce la sobrecarga. Entonces tenemos dos veces dos funciones con el mismo nombre pero de diferente firma. Entonces necesitamos alguna forma de distinguirlos. De todos modos, veamos qué tiene que decir un compilador sobre esto:
example/ex01/ $ make cc -c -o foobar.o foobar.c In file included from foobar.c:4: bar.h:1: error: conflicting types for ‘spam’ foo.h:1: note: previous declaration of ‘spam’ was here bar.h:2: error: conflicting types for ‘eggs’ foo.h:2: note: previous declaration of ‘eggs’ was here foobar.c: In function ‘main’: foobar.c:11: error: too few arguments to function ‘spam’ foobar.c:11: error: too few arguments to function ‘eggs’ make: *** [foobar.o] Error 1
De acuerdo, esto no fue una sorpresa, solo nos dijo lo que ya sabíamos, o al menos sospechamos.
Entonces, ¿podemos de alguna manera resolver esa colisión de identificadores sin modificar el código fuente o los encabezados de las bibliotecas originales? De hecho podemos.
Primero vamos a resolver los problemas de tiempo de compilación. Para esto, rodeamos el encabezado incluye con un montón de
#define
directivas de preprocesador que prefieren todos los símbolos exportados por la biblioteca. Más tarde, hacemos esto con un bonito y acogedor encabezado de envoltura, pero solo para demostrar lo que está sucediendo lo estamos haciendo textualmente en el archivo fuente foobar.c :ejemplo / ex02 / foobar.c
#include <stdio.h> #define spam foo_spam #define eggs foo_eggs # include "foo.h" #undef spam #undef eggs #define spam bar_spam #define eggs bar_eggs # include "bar.h" #undef spam #undef eggs int main() { const int new_bar_spam = 3; const double new_bar_eggs = 5.0f; printf("foo: spam = %d, eggs = %f\n", foo_spam(), foo_eggs() ); printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n", bar_spam(new_bar_spam), new_bar_spam, bar_eggs(new_bar_eggs), new_bar_eggs ); return 0; }
Ahora, si compilamos esto ...
example/ex02/ $ make cc -c -o foobar.o foobar.c cc foobar.o foo.o bar.o -o foobar bar.o: In function `spam': bar.c:(.text+0x0): multiple definition of `spam' foo.o:foo.c:(.text+0x0): first defined here bar.o: In function `eggs': bar.c:(.text+0x1e): multiple definition of `eggs' foo.o:foo.c:(.text+0x19): first defined here foobar.o: In function `main': foobar.c:(.text+0x1e): undefined reference to `foo_eggs' foobar.c:(.text+0x28): undefined reference to `foo_spam' foobar.c:(.text+0x4d): undefined reference to `bar_eggs' foobar.c:(.text+0x5c): undefined reference to `bar_spam' collect2: ld returned 1 exit status make: *** [foobar] Error 1
... primero parece que las cosas empeoraron. Pero mira de cerca: en realidad, la etapa de compilación fue bien. Es solo el enlazador el que ahora se queja de que hay símbolos que chocan y nos dice la ubicación (archivo fuente y línea) donde ocurre esto. Y como podemos ver, esos símbolos no tienen prefijo.
Echemos un vistazo a las tablas de símbolos con la utilidad nm :
example/ex02/ $ nm foo.o 0000000000000019 T eggs 0000000000000000 T spam 0000000000000008 C the_eggs 0000000000000004 C the_spams example/ex02/ $ nm bar.o 0000000000000019 T eggs 0000000000000000 T spam 0000000000000008 C the_eggs 0000000000000004 C the_spams
Así que ahora tenemos el desafío del ejercicio de anteponer esos símbolos en algún binario opaco. Sí, sé que en el transcurso de este ejemplo tenemos las fuentes y podría cambiar esto allí. Pero por ahora, asuma que solo tiene esos archivos .o , o un .a (que en realidad es solo un montón de .o ).
objcopy al rescate
Hay una herramienta especialmente interesante para nosotros: objcopy
objcopy funciona en archivos temporales, por lo que podemos usarlo como si estuviera operando en el lugar. Hay una opción / operación llamada --prefix-symbols y tiene 3 suposiciones de lo que hace.
Así que arrojemos a este amigo a nuestras obstinadas bibliotecas:
nm nos muestra que esto parecía funcionar:
example/ex03/ $ nm foo.o 0000000000000019 T foo_eggs 0000000000000000 T foo_spam 0000000000000008 C foo_the_eggs 0000000000000004 C foo_the_spams example/ex03/ $ nm bar.o 000000000000001e T bar_eggs 0000000000000000 T bar_spam 0000000000000008 C bar_the_eggs 0000000000000004 C bar_the_spams
Intentemos vincular todo esto:
Y de hecho, funcionó:
example/ex03/ $ ./foobar foo: spam = 0, eggs = 0.000000 bar: old spam = 0, new spam = 3 ; old eggs = 0.000000, new eggs = 5.000000
Ahora dejo como ejercicio para el lector implementar una herramienta / script que extrae automáticamente los símbolos de una biblioteca usando nm , escribe un archivo de encabezado contenedor de la estructura
/* wrapper header wrapper_foo.h for foo.h */ #define spam foo_spam #define eggs foo_eggs /* ... */ #include <foo.h> #undef spam #undef eggs /* ... */
y aplica el prefijo de símbolo a los archivos de objeto de la biblioteca estática mediante objcopy .
¿Qué pasa con las bibliotecas compartidas?
En principio, se podría hacer lo mismo con las bibliotecas compartidas. Sin embargo, las bibliotecas compartidas, el nombre lo indica, se comparten entre varios programas, por lo que jugar con una biblioteca compartida de esta manera no es una buena idea.
No podrás escribir un envoltorio de trampolín. Peor aún, no puede vincular con la biblioteca compartida a nivel de archivo de objeto, pero se ve obligado a realizar una carga dinámica. Pero esto merece su propio artículo.
Estén atentos y feliz codificación.
fuente
objcopy
.objcopy --prefix-symbols
... +1!Esto no es solo una extensión de los compiladores modernos; el estándar C actual también requiere que el compilador admita nombres externos razonablemente largos. Olvidé la longitud exacta, pero ahora son algo así como 31 caracteres si no recuerdo mal.
Entonces estás atascado. Quejarse con el autor de la biblioteca. Una vez encontré un error de este tipo en el que los usuarios de mi aplicación no podían compilarla en Debian debido a la
libSDL
vinculación de Debianlibsoundfile
, que (al menos en ese momento) contaminó el espacio de nombres global horriblemente con variables comodsp
(¡No bromeo!). Me quejé con Debian y ellos arreglaron sus paquetes y enviaron la corrección en sentido ascendente, donde supongo que se aplicó, ya que nunca más escuché del problema.Realmente creo que este es el mejor enfoque, porque resuelve el problema para todos. . Cualquier truco local que hagas dejará el problema en la biblioteca para que el próximo usuario desafortunado lo encuentre y luche de nuevo.
Si realmente necesita una solución rápida y tiene la fuente, puede agregar un montón de
-Dfoo=crappylib_foo -Dbar=crappylib_bar
etc. al archivo MAKE para solucionarlo. Si no es así, use laobjcopy
solución que encontró.fuente
Si está utilizando GCC, el modificador de enlazador --allow-multiple-definition es una útil herramienta de depuración. Esto obliga al enlazador a usar la primera definición (y no a quejarse). Más sobre esto aquí .
Esto me ha ayudado durante el desarrollo cuando tengo la fuente de una biblioteca proporcionada por el proveedor disponible y necesito rastrear una función de biblioteca por alguna razón u otra. El conmutador le permite compilar y vincular en una copia local de un archivo de origen y aún vincular a la biblioteca del proveedor estático no modificado. No olvide retirar el interruptor de los símbolos de marca una vez que se complete el viaje de descubrimiento. El código de liberación de envío con colisiones intencionales de espacios de nombres es propenso a errores, incluidas las colisiones involuntarias de espacios de nombres.
fuente