o que dificulta, digamos, o compilador visual C ++ no Windows gerar um arquivo executável binário linux?
Além de uma falta de vontade de fazer isso por parte da Microsoft, absolutamente nada. Os obstáculos não são técnicos.
As cadeias de ferramentas de desenvolvimento são apenas programas que recebem informações e produzem resultados. O Visual C ++ produz um assembly x86 e, em seguida, usa um assembler para convertê-lo em um arquivo de objeto COFF. Se a Microsoft quisesse gerar ELF, é apenas código: a montagem entra, a ELF sai. Não há nada mágico em arquivos ou bibliotecas de objetos; são apenas bolhas de dados em um formato bem compreendido.
No começo da era da pedra, a compilação cruzada era muito mais difícil porque, na maioria das vezes, você escrevia a cadeia de ferramentas para sua plataforma de destino na montagem da plataforma em que seria executada. Isso significava que, se tudo o que havia no mundo fossem as arquiteturas VAX, M68K e Alpha, um conjunto completo de compiladores cruzados exigiria a criação de nove deles, a maioria do zero. (VAX para VAX, VAX para M68K, VAX para Alpha, M68K para VAX, M68K para M68K etc.) Isso é um exagero, pois partes do compilador VAX podem ser reutilizadas e anexado aos geradores de código para cada destino (por exemplo, VAX, M68K e Alpha, cada um escrito para o VAX.)
Esse problema desapareceu quando começamos a escrever compiladores em um idioma que não estava vinculado a um processador específico, como um C. Seguir essa rota significa que você escreve toda a cadeia de ferramentas uma vez em C e usa uma plataforma escrita para a local Compilador C para construí-lo. (Você costuma usar o compilador para se recompilar após ter sido inicializado no compilador da plataforma local, mas isso é outra discussão.) O resultado disso é que a criação de um compilador cruzado se tornou essencialmente o mesmo esforço que a compilação de um compilador nativo no a plataforma local. A única diferença significativa é que, em algum ponto do processo de compilação, você pediu para compilar no gerador de código para sua plataforma de destino, em vez daquele na plataforma local, que seria a escolha lógica.
À medida que a arquitetura dos compiladores evoluiu, tornou-se conveniente incluir e construir todos os geradores de código com o produto e selecionar qual deles seria usado em tempo de execução. Clang / LLVM faz isso, e tenho certeza que existem outros.
Depois de ter uma cadeia de ferramentas de trabalho (compilador, montador, vinculador), as bibliotecas são construídas a partir de fontes e, eventualmente, você acaba com tudo o que precisa para produzir um arquivo executável para outra plataforma.