Compatibilidade com a biblioteca C++

O NDK é compatível com várias bibliotecas do ambiente de execução C++. Este documento traz informações sobre essas bibliotecas, como usá-las e as desvantagens de cada uma.

Bibliotecas de ambiente de execução C++

Tabela 1. Ambientes de execução e recursos do NDK C++.

Nome Recursos
libc++ Compatibilidade com C++17.
system new e delete. (Obsoleto em r18.)
none Nenhum cabeçalho, C ++ limitado.

A libc++ está disponível como biblioteca estática e compartilhada.

libc++

libc++ do LLVM é uma biblioteca C++ padrão usada pelo SO Android desde a versão Lollipop. Desde o NDK r18, ela é a única STL disponível no NDK.

A biblioteca compartilhada para libc++ é libc++_shared.so, e a biblioteca estática é libc++_static.a.

A biblioteca libc++ é usada sob a licença “no estilo BSD” da Universidade de Illinois e também sob a licença do Instituto de Tecnologia de Massachusetts (MIT, na sigla em inglês). Para saber mais, consulte o arquivo de licença.

system

O ambiente de execução system refere-se a /system/lib/libstdc++.so. Essa biblioteca não deve ser confundida com a libstdc ++ completa do GNU. No Android, libstdc ++ é apenas new e delete. Use libc++ como uma biblioteca C++ padrão completa.

O ambiente de execução system do C++ oferece compatibilidade com a ABI básica do ambiente de execução C++. Simplificando, essa biblioteca fornece new e delete. Diferentemente de outras opções disponíveis no NDK, não há compatibilidade com o gerenciamento de exceções ou com RTTI.

Não há compatibilidade com a biblioteca padrão além dos wrappers C++ para os cabeçalhos da biblioteca C, como <cstdio>. Se quiser uma STL, use uma das outras opções apresentadas nesta página.

none

Há também a opção de não ter nenhuma STL. Não há requisitos de vinculação ou licenciamento nesse caso. Não há cabeçalhos C++ padrão disponíveis.

Selecionar um ambiente de execução C++

Se você usa CMake, pode especificar um ambiente de execução da Tabela 1 com a variável ANDROID_STL no arquivo build.gradle do módulo. Para saber mais, consulte Como usar variáveis CMake.

Se você usa ndk-build, pode especificar um ambiente de execução da Tabela 1 com a variável APP_STL no arquivo Application.mk. Exemplo:

APP_STL := c++_shared
    

Você só pode selecionar um ambiente de execução para seu app e apenas em Application.mk.

Quando utilizados, os conjuntos de ferramentas autônomos usarão a STL compartilhada por padrão. Para usar a variante estática, adicione -static-libstdc++ às sinalizações do vinculador.

Considerações importantes

Ambientes de execução estáticos

Se todo o código nativo do seu app estiver em uma única biblioteca compartilhada, recomendamos o uso do ambiente de execução estático. Desse modo, o vinculador poderá incorporar e suprimir o máximo possível de código não usado, gerando um app otimizado e de tamanho reduzido. Isso também evita que o PackageManager e o vinculador dinâmico apresentem bugs em versões antigas do Android que dificultam o gerenciamento de várias bibliotecas compartilhadas e aumentam a propensão a erros.

Portanto, em C++, não é seguro definir mais de uma cópia da mesma função ou objeto em um único programa. Esse é um aspecto da Regra de uma definição (link em inglês) presente no padrão C++.

Ao usar um ambiente de execução estático (e bibliotecas estáticas em geral), é fácil violar essa regra acidentalmente. Por exemplo, o app a seguir viola esta regra.

# Application.mk
    APP_STL := c++_static
    
# Android.mk

    include $(CLEAR_VARS)
    LOCAL_MODULE := foo
    LOCAL_SRC_FILES := foo.cpp
    include $(BUILD_SHARED_LIBRARY)

    include $(CLEAR_VARS)
    LOCAL_MODULE := bar
    LOCAL_SRC_FILES := bar.cpp
    LOCAL_SHARED_LIBRARIES := foo
    include $(BUILD_SHARED_LIBRARY)
    

Nesse caso, a STL, incluindo dados globais e construtores estáticos, estará presente nas duas bibliotecas. O comportamento do ambiente de execução desse app é indefinido, e as falhas são muito comuns na prática. Outros possíveis problemas incluem:

  • A memória alocada em uma biblioteca e liberada em outra causa vazamento de memória ou corrupção do heap.
  • As exceções geradas em libfoo.so deixarão de ser detectadas em libbar.so, fazendo com que seu app falhe.
  • O buffer de std::cout não funciona corretamente.

Além dos problemas comportamentais envolvidos, vincular o ambiente de execução estático a várias bibliotecas duplicará o código em todas as bibliotecas compartilhadas, aumentando o tamanho do app.

Em geral, só é possível usar uma variante estática do ambiente de execução C++ se houver somente uma biblioteca compartilhada no seu app.

Ambientes de execução compartilhados

Caso seu app inclua várias bibliotecas compartilhadas, use libc++_shared.so.

No Android, a libc++ usada pelo NDK não faz parte do SO. Isso dá aos usuários do NDK acesso aos recursos e correções de bugs mais recentes da libc++, mesmo ao segmentar versões antigas do Android. A desvantagem é que, se você usar libc++_shared.so, será necessário incluí-lo no APK. Se você estiver criando o app com o Gradle, isso será feito automaticamente.

As versões antigas do Android tinham bugs no PackageManager e no vinculador dinâmico que faziam com que a instalação, a atualização e o carregamento de bibliotecas nativas não fossem feitos de modo confiável. Principalmente se o app for destinado a uma versão do Android anterior à 4.3 (API de nível 18 do Android) e você usar libc++_shared.so, será necessário carregar a biblioteca compartilhada antes de qualquer outra biblioteca que dependa dela.

O projeto ReLinker oferece soluções para todos os problemas de carregamento de biblioteca nativa conhecidos e costuma ser uma opção melhor do que desenvolver soluções próprias.

Uma STL por app

Anteriormente, o NDK era compatível com as bibliotecas libstdc++ e STLport do GNU, além da libc++. Se o app depende de bibliotecas pré-criadas em um NDK diferente daquele usado para criar seu app, garanta que ele faça isso de maneira compatível.

Os apps não devem usar mais de um ambiente de execução C++. As várias STLs não são compatíveis entre si. Por exemplo, o layout de std::string na libc++ não é o mesmo que em gnustl. Códigos criados com uma STL não poderão usar objetos criados com outra. Esse é apenas um exemplo. Existem muitas outras incompatibilidades.

Essa regra não se aplica somente ao seu código. Todas as dependências precisam usar a mesma STL que você selecionou. Se você precisa de uma dependência terceirizada de código fechado que usa a STL e não fornece uma biblioteca por STL, não será possível escolher a STL. Use a mesma STL como sua dependência.

É possível depender de duas bibliotecas incompatíveis entre si. Nessa situação, as únicas soluções são eliminar uma das dependências ou solicitar que o responsável forneça uma biblioteca compilada com a outra STL.

Exceções de C++

A libc++ é compatível com exceções de C++, mas elas são desativadas por padrão no ndk-build. Isso ocorre porque, anteriormente, as exceções C++ não estavam disponíveis no NDK. O CMake e os drivers de ferramentas autônomas têm exceções C++ ativadas por padrão.

Para ativar exceções em todo o app no ndk-build, adicione a seguinte linha ao arquivo Application.mk:

APP_CPPFLAGS := -fexceptions
    

Para ativar exceções em um único módulo do ndk-build, adicione a seguinte linha ao módulo correspondente no Android.mk:

LOCAL_CPP_FEATURES := exceptions
    

Como alternativa, você pode usar:

LOCAL_CPPFLAGS := -fexceptions
    

RTTI

Assim como as exceções, a libc++ é compatível com RTTI, mas esse recurso é desativado por padrão no ndk-build. A RTTI é ativada por padrão no CMake e nos conjuntos de ferramentas autônomos.

Para ativar a RTTI em todo o app no ndk-build, adicione a seguinte linha ao arquivo Application.mk:

APP_CPPFLAGS := -frtti
    

Para ativar a RTTI em um único módulo do ndk-build, adicione a seguinte linha ao módulo correspondente no Android.mk:

LOCAL_CPP_FEATURES := rtti
    

Como alternativa, você pode usar:

LOCAL_CPPFLAGS := -frtti