Compatibilidade com a biblioteca C++

O NDK é compatível com várias bibliotecas de 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++ moderno.
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++

A libc++ do LLVM (link em inglês) é 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.

Por padrão, o CMake usa qualquer versão de C++ definida como padrão pelo Clang (atualmente, C++14). Portanto, você precisará definir o CMAKE_CXX_STANDARD padrão como o valor adequado no arquivo CMakeLists.txt para usar recursos da versão C++17 ou mais recentes. Consulte a documentação do CMake sobre CMAKE_CXX_STANDARD (link em inglês) para ver mais detalhes.

O ndk-build também deixa a decisão para o Clang por padrão. Então, os usuários do ndk-build precisam usar APP_CPPFLAGS para adicionar -std=c++17 ou qualquer outra opção.

A biblioteca compartilhada para libc++ é libc++_shared.so, e a estática é libc++_static.a. Em casos típicos, o sistema de compilação processará o uso e o empacotamento dessas bibliotecas conforme necessário para o usuário. Para casos atípicos ou ao implementar seu próprio sistema de compilação, consulte o Guia de mantenedores do sistema de compilação ou o guia para usar outros sistemas de compilação.

O projeto LLVM está sob a licença Apache v2.0 com exceções do LLVM. Para saber mais, consulte o arquivo de licença (link em inglês).

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 a 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++

CMake

O padrão para o CMake é c++_static.

É possível especificar c++_shared, c++static, none ou system com a variável ANDROID_STL no arquivo build.gradle do módulo. Para saber mais, consulte a documentação do ANDROID_STL no CMake.

ndk-build

O padrão para o ndk-build é none.

Você pode especificar c++_shared, c++static, none ou system com a variável APP_STL no arquivo Application.mk. Exemplo:

APP_STL := c++_shared

O ndk-build só permite selecionar um ambiente de execução para seu app e apenas em Application.mk.

Usar clang diretamente

Se você estiver usando clang diretamente no seu sistema de compilação, o clang++ usará c++_shared por padrão. Para usar a variante estática, adicione -static-libstdc++ às sinalizações do vinculador. Observe que, embora a opção use o nome "libstdc++" por motivos históricos, o uso de libc++ também está correto.

Considerações importantes

Ambientes de execução estáticos

Se todo o código nativo do seu app está 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 essa 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ó será 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 é a mesma que faz parte do SO. Isso dá aos usuários do NDK acesso aos recursos e correções de bugs mais recentes da libc++, mesmo em versões antigas do Android. A desvantagem é que, se você usar a libc++_shared.so, será necessário incluí-la no app. Se estiver criando o app no 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 escritos com uma STL não poderão usar objetos escritos 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 é 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 conjuntos 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