De qué depende que un software o binario sea compatible con una máquina

Última actualización: diciembre 17, 2025
Autor: Isaac
  • La compatibilidad de un binario viene determinada por la combinación de ISA, ABI, API del sistema operativo, extensiones de CPU y formato de empaquetado.
  • Emulación, máquinas virtuales y capas de compatibilidad permiten ejecutar binarios en entornos para los que no fueron diseñados, con costes de rendimiento y complejidad.
  • En la Administración, el código fuente de los sistemas de gestión y control debe someterse a principios de legalidad, publicidad y participación democrática.
  • Las pruebas de compatibilidad planificadas y priorizadas son esenciales para validar que una aplicación funciona de forma estable en la diversidad real de dispositivos y plataformas.

hex Cuando intentas instalar un programa y el sistema te responde con un error de compatibilidad, no es simple mala suerte: hay un montón de capas técnicas, legales y organizativas que determinan si un software o binario puede ejecutarse en una máquina concreta. Desde la arquitectura del procesador, pasando por el sistema operativo y sus bibliotecas, hasta las normas jurídicas que regulan cómo se escribe el código, todo ello condiciona lo que puedes (y no puedes) ejecutar.

Además, en un mundo donde lo mismo trabajas con un PC con Windows, un servidor GNU/Linux, un móvil Android y sistemas de administración electrónica, entender de qué depende esa compatibilidad no es solo una curiosidad técnica: afecta a la interoperabilidad, a tus derechos como usuario, a la transparencia de la Administración y hasta a la forma en que se toman decisiones judiciales asistidas por algoritmos.

¿Qué significa realmente que un software sea compatible?

Cuando decimos que un programa es compatible con una máquina, estamos diciendo que ese software puede ejecutarse sin romperse en un entorno concreto: una combinación de hardware (CPU, memoria, dispositivos), sistema operativo (Windows, Linux, macOS, Android, etc.), bibliotecas, APIs, versiones, y a veces incluso en un contexto jurídico determinado.

A nivel técnico, la compatibilidad se mueve en varios niveles: código fuente, lenguaje ensamblador, binario final y entorno de ejecución. Cada nivel introduce restricciones distintas y puede romper la compatibilidad aunque los otros estén bien. Por debajo de todo está la ISA (arquitectura del conjunto de instrucciones), que es la interfaz abstracta que define qué instrucciones entiende una CPU, qué registros tiene, cómo accede a memoria o a dispositivos de E/S.

En paralelo, en muchos usos públicos (Hacienda, Justicia, universidades, administración electrónica) el propio software se convierte en una suerte de “norma secreta” que condiciona derechos. Si el código no se somete a los mismos requisitos de publicidad, transparencia y control democrático que las leyes, terminamos en una especie de dictadura técnica en la que “lo que dice la aplicación” parece inapelable.

Del código fuente al ejecutable: dónde empieza (y se rompe) la compatibilidad

c

Un programa arranca su vida como código fuente en un lenguaje de alto nivel (C, Java, Python, etc.). En principio, ese texto es independiente de la máquina: puedes copiar el mismo archivo .c o .py entre equipos muy distintos y seguir leyéndolo y modificándolo sin problemas.

  ¿Cómo se puede dañar la batería de un carro?

Sin embargo, esa aparente portabilidad tiene matices importantes. Si el código fuente usa llamadas al sistema (syscalls) específicas de un sistema operativo, deja de ser realmente portable: un código en C que use directamente la API de Windows no se puede compilar sin más en Linux, porque las funciones, parámetros y convenciones cambian por completo.

En cuanto a la arquitectura, mientras te quedas en lenguajes de alto nivel, el código fuente puede compilarse para diferentes ISAs: ARM, x86-64, RISC-V, SPARC, POWER, etc. Aquí entra en juego la compilación cruzada: desde una máquina x86 puedes generar binarios para ARM, siempre que uses el compilador adecuado y las bibliotecas correctas.

Todo cambia si escribes directamente en lenguaje ensamblador. Ahí sí que ya estás casado con una ISA concreta: un programa en ASM para RISC-V no se puede ejecutar en ARM, aunque ambas sean arquitecturas RISC, porque el repertorio de instrucciones, los registros, los modos de direccionamiento y hasta el formato de las instrucciones difieren.

La transformación de código fuente en código máquina se hace mediante compilación (o interpretación/JIT), y el resultado es un binario que solo tiene sentido para una combinación muy concreta de arquitectura y sistema operativo. A partir de ese momento, el margen de compatibilidad se reduce drásticamente.

ISA, ABI, API y formato binario: el cóctel que decide si un binario arranca

El ejecutable final es un fichero de unos y ceros ajustado a un formato binario específico (ELF, PE, Mach-O, etc.) y a una arquitectura concreta. Ese binario incluye instrucciones de máquina, referencias a bibliotecas, tablas de símbolos y metadatos que el sistema operativo usa para cargarlo en memoria y enlazarlo.

La ISA (Instruction Set Architecture) marca qué instrucciones concretas puede ejecutar la CPU: suma, saltos, operaciones vectoriales, manejo de interrupciones, tipos de datos nativos, etc. Un binario x86-64 para Linux no tiene ninguna posibilidad de ejecutar en una CPU ARM nativa sin algún tipo de traducción o emulación intermedia, aunque el sistema operativo sea también Linux.

Sobre esa ISA se asienta la ABI (Application Binary Interface), que define cómo se comunican los binarios con el sistema operativo: convenciones de llamadas a funciones, cómo se pasan parámetros, qué registros se preservan, cómo son las estructuras en memoria, cómo se realizan las llamadas al sistema, etc. La ABI es la pieza clave de la portabilidad binaria: si cambias la ABI, un binario antiguo se puede volver inejecutable aunque la ISA siga siendo la misma.

La API del sistema operativo (las funciones que expondrán las bibliotecas del sistema y el propio kernel) completa el cuadro. Aunque dos sistemas compartan ISA, si sus APIs y ABIs divergen (caso típico entre diferentes Unix-like), los binarios no se podrán usar directamente salvo que exista una capa de compatibilidad que traduzca esas interfaces.

  ¿Qué programa sustituye siglo 21?

Por eso un ejecutable ELF compilado para Linux x86-64 no sirve directamente en FreeBSD x86-64, o en Solaris x86-64, ni siquiera aunque todos sean sistemas *nix. Haría falta que el sistema destino implemente una compatibilidad binaria que traduzca las syscalls y proporcione un entorno de bibliotecas y ABI “clónico” del original.

Si además el fabricante introduce cambios de calado en el kernel, en la API de librerías del sistema o en la ABI (por ejemplo, eliminando o modificando funciones clave), es fácil que un binario compilado para una versión concreta deje de funcionar en una versión anterior o posterior, rompiendo la retrocompatibilidad. Lo mismo ocurre cuando en una ISA se eliminan instrucciones antiguas que algunos binarios seguían usando.

Extensiones de CPU y microcompatibilidad: cuando la ISA “básica” no basta

binario

Aun dentro de una misma familia de procesadores, la compatibilidad no está garantizada al 100 %. Un binario puede estar compilado para aprovechar extensiones específicas de la CPU (MMX, SSE, AVX, AVX2, AVX-512, NEON, etc.) que solo están presentes en ciertos modelos o marcas.

Por ejemplo, dentro del mundo x86-64, hay CPUs de Intel, AMD, VIA y otros fabricantes. Si un compilador genera código que exige AVX-512, ese ejecutable solo arrancará en procesadores que implementen AVX-512. En una CPU que carece de ese set de instrucciones, la ejecución puede provocar excepciones ilegales o directamente no poder iniciarse.

Muchas distribuciones y proyectos mantienen versiones de sus paquetes compiladas con un perfil “conservador” (usando solo la ISA base y extensiones muy comunes) y otras más agresivas optimizadas para ciertas familias de CPU. Las primeras sacrifican algo de rendimiento para maximizar la compatibilidad, mientras que las segundas exprimen la máquina pero restringen el universo de usuarios potenciales.

Este mismo principio aplica a otros elementos de hardware: tarjetas gráficas, chipsets de red, controladores de almacenamiento, etc. Un software que tire de APIs gráficas avanzadas o de drivers muy específicos puede fallar en máquinas donde esos componentes no estén soportados o se comporten de forma distinta.

Fragmentación en GNU/Linux: distros, formatos de paquetes y binarios “universales”

En el ecosistema GNU/Linux, además de la arquitectura y del sistema operativo, hay un problema extra: la fragmentación entre distribuciones, gestores de paquetes y formatos (.deb, .rpm, .pkg.tar.zst, etc.).

Un binario compilado y empaquetado como .deb para Debian o Ubuntu puede no instalarse (o no funcionar correctamente) en una distro basada en RPM como Fedora u openSUSE, aunque la arquitectura sea la misma. Cambian rutas de instalación, versiones de librerías, herramientas de arranque de servicios, scripts de postinstalación… y eso rompe la compatibilidad práctica.

  ¿Cómo cambiar el formato de una canción M4A a MP3?

Para reducir este caos, han surgido formatos de paquetes “universales” como AppImage, Snap o Flatpak, que empaquetan junto a la aplicación la mayoría de dependencias y definen un entorno de ejecución más homogéneo. No eliminan por completo las diferencias entre distros, pero sí facilitan que el mismo paquete pueda ejecutarse en distintas variantes de Linux con menos sorpresas.

En paralelo, algunos proyectos persiguen la convergencia entre plataformas: marcos de trabajo que permiten desarrollar una sola aplicación y desplegarla en escritorio, móvil y otros dispositivos con cambios mínimos, apoyándose en runtimes comunes y APIs estables.

¿Se pueden saltar las restricciones de compatibilidad técnica?

Aunque parezca que la compatibilidad binaria es una frontera rígida, en la práctica existen varias estrategias para ejecutar software “foráneo” en máquinas y sistemas para los que no fue diseñado.

Una de las más conocidas son los emuladores de hardware, que simulan por software una arquitectura completa distinta: consolas antiguas, máquinas arcade, sistemas de 8/16 bits, o incluso ordenadores personales como los PC compatibles con MS-DOS. DOSBox, por ejemplo, recrea un entorno compatible con MS-DOS en sistemas modernos para ejecutar juegos y aplicaciones de la época.

Las máquinas virtuales (VirtualBox, VMware, KVM, Hyper-V, etc.) no emulan necesariamente una ISA distinta, pero sí ofrecen un hardware virtualizado sobre el que puedes instalar otro sistema operativo completo. Es decir, no haces compatible el binario con tu SO, sino que creas dentro de tu máquina un SO anfitrión que sí es compatible con ese binario.

Otra vía son las capas de compatibilidad, que no emulan hardware sino APIs y ABIs de otros sistemas. Wine, por ejemplo, traduce llamadas del mundo Windows (PE, Win32, DirectX en parte, etc.) al entorno Linux u otros *nix, permitiendo ejecutar muchos binarios de Windows sin tener Windows instalado. Darling hace algo similar con binarios Mach-O de macOS sobre Linux.

También hay casos de compatibilidad binaria “entre primos hermanos” *nix. Sistemas como FreeBSD pueden implementar una capa de compatibilidad con Linux capaz de ejecutar binarios ELF pensados para Linux. Para ello, el kernel traduce las syscalls de Linux a sus equivalentes BSD y provee bibliotecas compatibles. Esto exige ajustar la ABI y mantener un juego bastante completo de interfaces equivalentes.

Sistemas como Solaris también llegaron a ofrecer compatibilidad con binarios Linux mediante capas como lxrun, y Linux tuvo en su día iBCS2 para ejecutar binarios de otros Unix. Todos estos esfuerzos comparten la misma idea: interponer una capa que “hable” la ABI del sistema original y la traduzca al sistema real de ejecución.

Artículo relacionado:
Cómo usar software antiguo en Windows 10