Suponha que eu tenha três objetos compilados, todos produzidos pelo mesmo compilador / versão :
- A foi compilado com o padrão C ++ 11
- B foi compilado com o padrão C ++ 14
- C foi compilado com o padrão C ++ 17
Para simplificar, vamos supor que todos os cabeçalhos foram escritos em C ++ 11, usando apenas construções cuja semântica não mudou entre as três versões padrão e, portanto, quaisquer interdependências foram expressas corretamente com a inclusão do cabeçalho e o compilador não fez objeções.
Quais combinações desses objetos é e não é seguro vincular em um único binário? Por quê?
EDITAR: respostas cobrindo os principais compiladores (por exemplo, gcc, clang, vs ++) são bem-vindas
std::string
implementação ativa em libstdc ++ é independente do-std
modo usado . Esta é uma propriedade importante, justamente para dar suporte a situações como os OP's. Você pode usar o novostd::string
no código C ++ 03, e você pode usar o antigostd::string
no código C ++ 11 (consulte o link no comentário posterior de Matteo).Respostas:
Para o GCC , é seguro vincular qualquer combinação dos objetos A, B e C. Se todos forem construídos com a mesma versão, então são compatíveis com ABI, a versão padrão (ou seja, a
-std
opção) não faz nenhuma diferença.Por quê? Porque essa é uma propriedade importante de nossa implementação que trabalhamos muito para garantir.
Onde você tem problemas é vincular objetos compilados com versões diferentes do GCC e usar recursos instáveis de um novo padrão C ++ antes que o suporte do GCC para esse padrão seja concluído. Por exemplo, se você compilar um objeto usando GCC 4.9
-std=c++11
e outro objeto com GCC 5 e-std=c++11
você terá problemas. O suporte a C ++ 11 era experimental no GCC 4.x, portanto, havia alterações incompatíveis entre o GCC 4.9 e 5 versões dos recursos do C ++ 11. Da mesma forma, se você compilar um objeto com GCC 7 e-std=c++17
outro objeto com GCC 8 e-std=c++17
terá problemas, porque o suporte a C ++ 17 no GCC 7 e 8 ainda é experimental e em evolução.Por outro lado, qualquer combinação dos seguintes objetos funcionará (embora veja a nota abaixo sobre a
libstdc++.so
versão):-std=c++03
-std=c++11
-std=c++17
Isso ocorre porque o suporte ao C ++ 03 é estável em todas as três versões do compilador usadas e, portanto, os componentes do C ++ 03 são compatíveis entre todos os objetos. O suporte a C ++ 11 é estável desde GCC 5, mas o objeto D não usa nenhum recurso do C ++ 11, e os objetos E e F usam versões em que o suporte a C ++ 11 é estável. O suporte a C ++ 17 não é estável em nenhuma das versões do compilador usadas, mas apenas o objeto F usa recursos do C ++ 17 e, portanto, não há problema de compatibilidade com os outros dois objetos (os únicos recursos que eles compartilham vêm do C ++ 03 ou C ++ 11, e as versões usadas tornam essas partes OK). Se mais tarde você quisesse compilar um quarto objeto, G, usando GCC 8 e
-std=c++17
então precisaria recompilar F com a mesma versão (ou não vincular a F) porque os símbolos do C ++ 17 em F e G são incompatíveis.A única ressalva para a compatibilidade descrita acima entre D, E e F é que seu programa deve usar a
libstdc++.so
biblioteca compartilhada do GCC 7 (ou posterior). Como o objeto F foi compilado com GCC 7, você precisa usar a biblioteca compartilhada dessa versão, porque compilar qualquer parte do programa com GCC 7 pode introduzir dependências em símbolos que não estão presentes nolibstdc++.so
GCC 4.9 ou GCC 5. Da mesma forma, se você vinculou ao objeto G, construído com GCC 8, você precisaria usar olibstdc++.so
do GCC 8 para garantir que todos os símbolos necessários para G sejam encontrados. A regra simples é garantir que a biblioteca compartilhada que o programa usa em tempo de execução seja pelo menos tão nova quanto a versão usada para compilar qualquer um dos objetos.Outra advertência ao usar o GCC, já mencionada nos comentários sobre sua pergunta, é que, desde o GCC 5, existem duas implementações
std::string
disponíveis em libstdc ++. As duas implementações não são compatíveis com link (eles têm nomes mutilados diferentes, então não podem ser ligados entre si), mas podem coexistir no mesmo binário (eles têm nomes mutilados diferentes, então não entre em conflito se um objeto usarstd::string
e o outros usosstd::__cxx11::string
). Se seus objetos usamstd::string
, geralmente eles devem ser compilados com a mesma implementação de string. Compile com-D_GLIBCXX_USE_CXX11_ABI=0
para selecionar agcc4-compatible
implementação original , ou-D_GLIBCXX_USE_CXX11_ABI=1
para selecionar a novacxx11
implementação (não se deixe enganar pelo nome, ele também pode ser usado em C ++ 03, é chamadocxx11
porque está em conformidade com os requisitos do C ++ 11). Qual implementação é o padrão depende de como o GCC foi configurado, mas o padrão sempre pode ser substituído em tempo de compilação com a macro.fonte
A resposta tem duas partes. Compatibilidade no nível do compilador e compatibilidade no nível do vinculador. Vamos começar com o primeiro.
Usar o mesmo compilador significa que o mesmo cabeçalho da biblioteca padrão e arquivos de origem (os primeiros associados ao compilador) serão usados independentemente do padrão C ++ de destino. Portanto, os arquivos de cabeçalho da biblioteca padrão são escritos para serem compatíveis com todas as versões C ++ suportadas pelo compilador.
Dito isso, se as opções do compilador usadas para compilar uma unidade de tradução especificam um determinado padrão C ++, quaisquer recursos que estão disponíveis apenas em padrões mais novos não devem estar acessíveis. Isso é feito usando a
__cplusplus
diretiva. Veja o arquivo de origem do vetor para um exemplo interessante de como ele é usado. Da mesma forma, o compilador rejeitará quaisquer recursos sintáticos oferecidos pelas versões mais recentes do padrão.Tudo isso significa que sua suposição só pode ser aplicada aos arquivos de cabeçalho que você escreveu. Esses arquivos de cabeçalho podem causar incompatibilidades quando incluídos em diferentes unidades de tradução direcionadas a diferentes padrões C ++. Isso é discutido no Anexo C do padrão C ++. Existem 4 cláusulas, discutirei apenas a primeira e mencionarei brevemente o resto.
C.3.1 Cláusula 2: convenções lexicais
As aspas simples delimitam um literal de caractere em C ++ 11, enquanto são separadores de dígitos em C ++ 14 e C ++ 17. Suponha que você tenha a seguinte definição de macro em um dos arquivos de cabeçalho C ++ 11 puros:
Considere duas unidades de tradução que incluem o arquivo de cabeçalho, mas visam C ++ 11 e C ++ 14, respectivamente. Ao direcionar o C ++ 11, a vírgula entre aspas não é considerada um separador de parâmetro; existe apenas um parâmetro. Portanto, o código seria equivalente a:
Por outro lado, ao direcionar o C ++ 14, as aspas simples são interpretadas como separadores de dígitos. Portanto, o código seria equivalente a:
O ponto aqui é que usar aspas simples em um dos arquivos de cabeçalho C ++ 11 puros pode resultar em erros surpreendentes nas unidades de tradução voltadas para C ++ 14/17. Portanto, mesmo se um arquivo de cabeçalho for escrito em C ++ 11, ele deve ser escrito com cuidado para garantir que seja compatível com as versões posteriores do padrão. A
__cplusplus
diretiva pode ser útil aqui.As outras três cláusulas da norma incluem:
C.3.2 Cláusula 3: conceitos básicos
C.3.3 Cláusula 7: declarações
C.3.4 Cláusula 27: biblioteca de entrada / saída
As incompatibilidades potenciais entre C ++ 14 e C ++ 17 são discutidas em C.4. Como todos os arquivos de cabeçalho não padrão são escritos em C ++ 11 (conforme especificado na pergunta), esses problemas não ocorrerão, portanto, não os mencionarei aqui.
Agora vou discutir a compatibilidade no nível do vinculador. Em geral, os motivos potenciais para incompatibilidades incluem o seguinte:
main
ponto de entrada.Se o formato do arquivo de objeto resultante depender do padrão C ++ de destino, o vinculador deve ser capaz de vincular os diferentes arquivos de objeto. No GCC, LLVM e VC ++, felizmente esse não é o caso. Ou seja, o formato dos arquivos de objetos é o mesmo, independentemente do padrão de destino, embora seja altamente dependente do próprio compilador. Na verdade, nenhum dos vinculadores de GCC, LLVM e VC ++ requer conhecimento sobre o padrão C ++ de destino. Isso também significa que podemos vincular arquivos-objeto que já estão compilados (vinculando estaticamente o tempo de execução).
Se a rotina de inicialização do programa (a função que chama
main
) for diferente para diferentes padrões C ++ e as diferentes rotinas não forem compatíveis entre si, então não seria possível vincular os arquivos-objeto. No GCC, LLVM e VC ++, felizmente esse não é o caso. Além disso, a assinatura damain
função (e as restrições que se aplicam a ela, consulte a Seção 3.6 do padrão) é a mesma em todos os padrões C ++, portanto, não importa em qual unidade de tradução ela existe.Em geral, o WPO pode não funcionar bem com arquivos-objeto compilados usando padrões C ++ diferentes. Isso depende exatamente de quais estágios do compilador exigem conhecimento do padrão de destino e quais não, e do impacto que isso tem nas otimizações entre procedimentos que cruzam arquivos de objeto. Felizmente, GCC, LLVM e VC ++ são bem projetados e não têm esse problema (não que eu saiba).
Portanto, GCC, LLVM e VC ++ foram projetados para permitir compatibilidade binária em diferentes versões do padrão C ++. Este não é realmente um requisito do padrão em si.
A propósito, embora o compilador VC ++ ofereça a opção std , que permite direcionar uma versão específica do padrão C ++, ele não oferece suporte ao direcionamento C ++ 11. A versão mínima que pode ser especificada é C ++ 14, que é o padrão a partir da atualização 3 do Visual C ++ 2013 para compilar diferentes unidades de tradução que visam diferentes versões do padrão C ++, o que no mínimo quebraria o WPO.
CAVEAT: Minha resposta pode não ser completa ou muito precisa.
fonte
Os novos padrões C ++ vêm em duas partes: recursos de linguagem e componentes de biblioteca padrão.
Como você quer dizer com novo padrão , mudanças no idioma em si (por exemplo, variado para) quase não há problema (às vezes existem conflitos em cabeçalhos de bibliotecas de terceiros com recursos de linguagem padrão mais recentes).
Mas a biblioteca padrão ...
Cada versão do compilador vem com uma implementação da biblioteca padrão C ++ (libstdc ++ com gcc, libc ++ com clang, biblioteca padrão MS C ++ com VC ++, ...) e exatamente uma implementação, não muitas implementações para cada versão padrão. Além disso, em alguns casos, você pode usar outra implementação da biblioteca padrão além do compilador fornecido. Você deve se preocupar em vincular uma implementação de biblioteca padrão mais antiga a uma mais recente.
O conflito que pode ocorrer entre bibliotecas de terceiros e seu código é a biblioteca padrão (e outras bibliotecas) que se vincula a essas bibliotecas de terceiros.
fonte