Problema de interfaz binaria frágil - Fragile binary interface problem
El frágil problema de la interfaz binaria o FBI es un defecto de ciertos compiladores de lenguajes de programación orientados a objetos , en los que los cambios internos a una biblioteca de clases subyacente pueden hacer que las bibliotecas o programas descendientes dejen de funcionar. Es un ejemplo de fragilidad del software .
Este problema se denomina más a menudo problema de la clase base frágil o FBC ; sin embargo, ese término tiene un sentido más amplio.
Porque
El problema ocurre debido a un "atajo" usado con compiladores para muchos lenguajes orientados a objetos (OO) comunes, una característica de diseño que se mantuvo cuando los lenguajes OO estaban evolucionando de lenguajes de programación estructurados no OO anteriores como C y Pascal .
En estos lenguajes no había objetos en el sentido moderno, pero había una construcción similar conocida como registro (o "estructura" en C) que contenía una variedad de información relacionada en una sola pieza de memoria. Se accedió a las partes dentro de un registro en particular realizando un seguimiento de la ubicación de inicio del registro y conociendo el desplazamiento desde ese punto de inicio hasta la parte en cuestión. Por ejemplo, un registro de "persona" puede tener un nombre, apellido e inicial del segundo nombre, para acceder a la inicial thisPerson.middleInitialque escribe el programador, que el compilador convierte en algo parecido a = location(thisPerson) + offset(middleInitial). Las CPU modernas suelen incluir instrucciones para este tipo de acceso común.
Cuando se desarrollaron por primera vez los compiladores de lenguajes orientados a objetos, se utilizó gran parte de la tecnología de compilación existente y los objetos se construyeron sobre el concepto de registro. En estos lenguajes, se hacía referencia a los objetos por su punto de partida y se accedía a sus datos públicos, conocidos como "campos", a través del desplazamiento conocido. En efecto, el único cambio fue agregar otro campo al registro, que está configurado para apuntar a una tabla de método virtual inmutable para cada clase, de modo que el registro describa tanto sus datos como sus métodos (funciones). Cuando se compilan, las compensaciones se utilizan para acceder tanto a los datos como al código (a través de la tabla de métodos virtuales).
Síntomas
Esto genera un problema en los programas más grandes cuando se construyen a partir de bibliotecas . Si el autor de la biblioteca cambia el tamaño o el diseño de los campos públicos dentro del objeto, las compensaciones ahora no son válidas y el programa ya no funcionará. Este es el problema del FBI.
Aunque se puede esperar que los cambios en la implementación causen problemas, lo insidioso del FBI es que nada realmente cambió, solo el diseño del objeto que está oculto en una biblioteca compilada. Uno podría esperar que si uno cambia doSomethinga doSomethingElseeso podría causar un problema, pero en este caso uno puede causar problemas sin cambiar doSomething, puede ser causado tan fácilmente como mover líneas de código fuente para mayor claridad. Peor aún, el programador tiene poco o ningún control sobre el diseño resultante generado por el compilador, haciendo que este problema esté casi completamente oculto a la vista.
En bibliotecas o programas orientados a objetos complejos , las clases de más alto nivel pueden heredar de decenas de clases. Cada una de esas clases base también podría ser heredada por cientos de otras clases. Estas clases base son frágiles porque un pequeño cambio en una de ellas podría causar problemas a cualquier clase que herede de ella, ya sea directamente o de otra clase que lo haga. Esto puede hacer que la biblioteca colapse como un castillo de naipes, ya que muchas clases se dañan con un cambio a una clase base. Es posible que el problema no se note cuando se escriben las modificaciones si el árbol de herencia es complejo. De hecho, el desarrollador que modifica la clase base generalmente desconoce qué clases, desarrolladas por otros, la están usando.
Soluciones
Idiomas
Una solución al frágil problema de la interfaz binaria es escribir un lenguaje que sepa que existe el problema y que no permita que suceda en primer lugar. La mayoría de los lenguajes OO escritos a medida, a diferencia de los que evolucionaron a partir de lenguajes anteriores, construyen todas sus tablas de desplazamiento en el momento de la carga. Los cambios en el diseño de la biblioteca se "notarán" en ese momento. Otros lenguajes OO, como Self , construyen todo en tiempo de ejecución copiando y modificando los objetos que se encuentran en las bibliotecas y, por lo tanto, no tienen realmente una clase base que pueda ser frágil. Algunos lenguajes, como Java , tienen documentación extensa sobre qué cambios se pueden realizar sin causar problemas al FBI.
Otra solución es escribir un archivo intermedio que enumere las compensaciones y otra información de la etapa de compilación, conocida como metadatos. El enlazador luego usa esta información para corregirse cuando la biblioteca se carga en una aplicación. Las plataformas como .NET hacen esto.
Sin embargo, el mercado ha seleccionado lenguajes de programación como C ++ que de hecho son "dependientes de la posición" y por lo tanto exhiben FBI. En estos casos aún existen varias soluciones al problema. Uno pone la carga sobre el autor de la biblioteca al hacer que inserte una serie de objetos "marcadores de posición" en caso de que necesiten agregar funcionalidad adicional en el futuro (esto se puede ver en las estructuras utilizadas en la biblioteca de DirectX ). Esta solución funciona bien hasta que te quedas sin estos maniquíes, y no quieres agregar demasiados porque ocupa memoria.
Objective-C 2.0 proporciona variables de instancia no frágiles al tener un nivel adicional de direccionamiento indirecto para el acceso a variables de instancia.
Otra solución parcial es utilizar el patrón Bridge , a veces conocido como " Pimpl " ("Puntero a la implementación"). El marco Qt es un ejemplo de tal implementación. Cada clase define solo un miembro de datos, que es un puntero a la estructura que contiene los datos de implementación. Es poco probable que cambie el tamaño del puntero en sí (para una plataforma determinada), por lo que cambiar los datos de implementación no afecta el tamaño de la estructura pública. Sin embargo, esto no evita otros cambios importantes, como introducir métodos virtuales en una clase que no tiene ninguno o cambiar el gráfico de herencia.
Enlazadores
Otra solución requiere un enlazador más inteligente. En la versión original de Objective-C , el formato de biblioteca permitía múltiples versiones de una biblioteca e incluía algunas funciones para seleccionar la biblioteca adecuada cuando se llamaba. Sin embargo, esto no siempre fue necesario porque las compensaciones solo se necesitaban para los campos, ya que las compensaciones de métodos se recopilaban en tiempo de ejecución y no podían causar FBI. Dado que los métodos tienden a cambiar con más frecuencia que los campos, ObjC tuvo pocos problemas con el FBI en primer lugar, y los que tuvo se pudieron corregir con el sistema de control de versiones. Objective-C 2.0 agregó un "tiempo de ejecución moderno" que también resolvió el problema del FBI para los campos. Además, el lenguaje TOM utiliza compensaciones recopiladas en tiempo de ejecución para todo, lo que hace que el FBI sea imposible.
Usar bibliotecas estáticas en lugar de dinámicas siempre que sea posible es otra solución, ya que la biblioteca no se puede modificar sin volver a compilar la aplicación y actualizar las compensaciones que utiliza. Sin embargo, las bibliotecas estáticas tienen sus propios problemas serios, como un binario más grande y la imposibilidad de utilizar versiones más nuevas de la biblioteca "automáticamente" a medida que se introducen.
Arquitectura
En estos lenguajes, el problema se mitiga imponiendo la herencia única (ya que esto reduce la complejidad del árbol de herencia) y mediante el uso de interfaces en lugar de clases base con funciones virtuales , ya que las interfaces en sí mismas no contienen código, solo una garantía de que cada La firma del método que declare la interfaz será compatible con cada objeto que implemente la interfaz.
Método de distribución
Todo el problema se colapsa si el código fuente de las bibliotecas está disponible. Entonces una simple recopilación funcionará.