Compatibilité avec les bibliothèques C++

Le NDK est compatible avec plusieurs bibliothèques d'exécution C++. Ce document fournit des informations sur ces bibliothèques, les choix qu'elles impliquent et leur utilisation.

Bibliothèques d'exécution C++

Tableau 1. Environnements d'exécution C++ du NDK et fonctionnalités associées

Nom Fonctionnalités
libc++ Compatibilité avec le langage C++ moderne
system new et delete (obsolète depuis la version r18)
none Aucun en-tête, C++ limité

libc++ est disponible à la fois en tant que bibliothèque statique et partagée.

libc++

La bibliothèque libc++ de LLVM est la bibliothèque standard C++ utilisée par l'OS Android depuis Lollipop. À compter de NDK r18, il s'agit de la seule STL disponible dans le NDK.

CMake est défini par défaut sur la version de C++ utilisée par défaut par clang (actuellement C++14). Vous devez donc définir la norme CMAKE_CXX_STANDARD sur la valeur appropriée dans votre fichier CMakeLists.txt pour utiliser les fonctionnalités de C++17 ou version ultérieure. Pour en savoir plus, consultez la documentation de CMake sur CMAKE_CXX_STANDARD.

ndk-build laisse également clang décider par défaut. Par conséquent, les utilisateurs de ndk-build doivent utiliser APP_CPPFLAGS pour ajouter -std=c++17 ou toute autre version de leur choix.

La bibliothèque libc++ partagée se nomme libc++_shared.so et la bibliothèque statique, libc++_static.a. En règle générale, le système de compilation gère l'utilisation et le packaging de ces bibliothèques en fonction des besoins de l'utilisateur. Pour les cas atypiques ou lors de la mise en œuvre de votre propre système de compilation, consultez le document Build System Maintainers Guide ou le guide Utiliser le NDK avec d'autres systèmes de compilation.

Le projet LLVM est soumis à la licence Apache version 2.0 avec des exceptions pour LLVM. Pour en savoir plus, consultez le fichier de licence.

system

L'environnement d'exécution système se réfère à /system/lib/libstdc++.so. Cette bibliothèque ne doit pas être confondue avec la bibliothèque libstdc++ aux fonctionnalités complètes de GNU. Sous Android, libstdc++ ne dispose que de new et delete. Pour bénéficier d'une bibliothèque standard C++ aux fonctionnalités complètes, utilisez libc++.

L'environnement d'exécution système C++ est compatible avec l'ABI d'exécution C++ de base. Cette bibliothèque fournit essentiellement new et delete. Contrairement aux autres options disponibles dans le NDK, elle n'est pas compatible avec la gestion des exceptions ni avec RTTI.

Aucune compatibilité avec les bibliothèques standards n'est assurée, à l'exception des wrappers C++ pour les en-têtes de bibliothèque C tels que <cstdio>. Si vous souhaitez utiliser une STL, choisissez l'une des autres options présentées sur cette page.

none

Il est également possible de n'utiliser aucune STL. Dans ce cas, aucune exigence en matière d'association ou de licence ne s'applique. Aucun en-tête standard C++ n'est disponible.

Sélectionner un environnement d'exécution C++

CMake

La bibliothèque par défaut pour CMake est c++_static.

Vous pouvez spécifier c++_shared, c++_static, none ou system à l'aide de la variable ANDROID_STL dans votre fichier build.gradle au niveau du module. Pour en savoir plus, consultez la documentation sur ANDROID_STL dans CMake.

ndk-build

La bibliothèque par défaut pour ndk-build est none.

Vous pouvez spécifier c++_shared, c++_static, none ou system à l'aide de la variable APP_STL dans votre fichier Application.mk. Par exemple :

APP_STL := c++_shared

ndk-build ne vous permet de sélectionner qu'un seul environnement d'exécution pour votre application, et vous ne pouvez le faire que dans Application.mk.

Utiliser clang directement

Si vous utilisez clang directement dans votre propre système de compilation, clang++ utilise c++_shared par défaut. Pour utiliser la variante statique, ajoutez -static-libstdc++ aux options de votre éditeur de liens. Notez que bien que l'option utilise le nom "libstdc++" pour des raisons historiques, elle s'applique également à libc++.

Remarques importantes

Environnements d'exécution statiques

Si l'intégralité du code natif de votre application est contenu dans une seule bibliothèque partagée, nous vous recommandons d'utiliser l'environnement d'exécution statique. Cela permet à l'éditeur de liens d'intégrer et de nettoyer au maximum le code inutilisé, de façon à générer l'application la plus optimisée et la plus petite possible. Cela évite également les bugs du gestionnaire de packages et de l'éditeur de liens dynamique dans les anciennes versions d'Android, qui font que le traitement de plusieurs bibliothèques partagées est difficile et source d'erreurs.

Cela dit, en C++, il n'est pas prudent de définir plusieurs copies de la même fonction ou du même objet dans un même programme. Il s'agit de l'un des aspects de la règle de définition unique (ODR, One Definition Rule) présente dans la norme C++.

Lorsque vous utilisez un environnement d'exécution statique (et des bibliothèques statiques en général), il est facile d'enfreindre accidentellement cette règle. Par exemple, l'application suivante enfreint cette règle :

# 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)

Dans ce cas, la STL, y compris les données globales et les constructeurs statiques, est présente dans les deux bibliothèques. Le comportement de cette application lors de l'exécution n'est pas défini. En pratique, les plantages sont très courants. Les autres problèmes possibles sont les suivants :

  • La mémoire est allouée dans une bibliothèque et libérée dans l'autre, ce qui entraîne des fuites de mémoire ou la corruption du tas de mémoire.
  • Les exceptions générées dans libfoo.so ne sont pas détectées dans libbar.so, ce qui entraîne le plantage de l'application.
  • La mise en mémoire tampon de std::cout ne fonctionne pas correctement.

En plus de ces problèmes de comportement, l'association de l'environnement d'exécution statique avec plusieurs bibliothèques duplique le code dans chaque bibliothèque partagée, ce qui augmente la taille de l'application.

En général, vous ne pouvez utiliser une variante statique de l'environnement d'exécution C++ que si vous disposez d'une seule bibliothèque partagée dans votre application.

Environnements d'exécution partagés

Si votre application inclut plusieurs bibliothèques partagées, vous devez utiliser libc++_shared.so.

Sous Android, la libc++ utilisée par le NDK est différente de celle qui fait partie de l'OS. Cela permet aux utilisateurs du NDK d'accéder aux fonctionnalités et aux corrections de bugs les plus récentes pour libc++, même lorsqu'ils ciblent d'anciennes versions d'Android. En contrepartie, si vous utilisez libc++_shared.so, vous devez l'inclure dans votre application. Si vous compilez votre application avec Gradle, cette opération est gérée automatiquement.

Les anciennes versions d'Android comportaient des bugs dans le gestionnaire de packages et l'éditeur de liens dynamique qui affectaient la fiabilité de l'installation, de la mise à jour et du chargement des bibliothèques natives. En particulier, si votre application cible une version d'Android antérieure à Android 4.3 (niveau d'API Android 18) et si vous utilisez libc++_shared.so, vous devez charger la bibliothèque partagée avant toute autre bibliothèque qui en dépend.

Le projet ReLinker propose des solutions pour tous les problèmes connus de chargement de bibliothèques natives. Il s'agit généralement d'un meilleur choix que l'écriture de vos propres solutions de contournement.

Une STL par application

Autrefois, le NDK était compatible avec GNU libstdc++ et STLport en plus de libc++. Si votre application dépend de bibliothèques prédéfinies qui ont été compilées à partir d'un NDK différent de celui utilisé pour créer votre application, vous devez vous assurer que tout est compatible.

Une application ne doit pas utiliser plusieurs environnements d'exécution C++. Les différentes STL ne sont pas compatibles entre elles. Par exemple, la structure de std::string dans libc++ n'est pas la même que dans gnustl. Le code écrit avec une STL ne peut pas utiliser les objets écrits avec une autre. Ceci n'est qu'un exemple. Les incompatibilités sont nombreuses.

Cette règle ne s'applique pas qu'à votre code. Toutes vos dépendances doivent utiliser cette même STL que vous avez sélectionnée. Si vous utilisez une dépendance tierce de source fermée qui utilise la STL et ne fournit pas de bibliothèque par STL, vous n'avez pas le choix de la STL. Vous devez utiliser la même STL que votre dépendance.

Il est possible que vous dépendiez de deux bibliothèques incompatibles entre elles. Dans ce cas, les seules solutions possibles consistent à supprimer l'une des dépendances ou à demander à la personne qui en est responsable de fournir une bibliothèque reposant sur l'autre STL.

Exceptions C++

Les exceptions C++ sont compatibles avec libc++, mais sont désactivées par défaut dans ndk-build. En effet, les exceptions C++ n'étaient pas disponibles dans le NDK par le passé. Les exceptions C++ sont activées par défaut dans CMake et dans les chaînes d'outils autonomes.

Pour activer les exceptions pour l'ensemble de votre application dans ndk-build, ajoutez la ligne suivante à votre fichier Application.mk :

APP_CPPFLAGS := -fexceptions

Pour activer les exceptions pour un seul module ndk-build, ajoutez la ligne suivante au fichier Android.mk de ce module :

LOCAL_CPP_FEATURES := exceptions

Vous pouvez utiliser la ligne suivante à la place :

LOCAL_CPPFLAGS := -fexceptions

RTTI

Comme les exceptions, RTTI est compatible avec libc++, mais est désactivé par défaut dans ndk-build. RTTI est activé par défaut dans CMake et dans les chaînes d'outils autonomes.

Pour activer RTTI pour l'ensemble de votre application dans ndk-build, ajoutez la ligne suivante à votre fichier Application.mk :

APP_CPPFLAGS := -frtti

Pour activer RTTI pour un seul module ndk-build, ajoutez la ligne suivante au fichier Android.mk de ce module :

LOCAL_CPP_FEATURES := rtti

Vous pouvez utiliser la ligne suivante à la place :

LOCAL_CPPFLAGS := -frtti