Compatibilidad de la biblioteca C++

El NDK admite múltiples bibliotecas de tiempo de ejecución C++. En este documento, se proporciona información sobre estas bibliotecas, incluidos sus aspectos positivos y negativos, y cómo usarlas.

Bibliotecas de tiempo de ejecución C++

Tabla 1. Tiempos de ejecución y funciones de C++ del NDK.

Name Funciones
libc++ Compatibilidad con C++17.
sistema new y delete. (Obsoleta en r18).
ninguna Sin encabezados, C++ limitado.

libc++ está disponible tanto para bibliotecas compartidas como estáticas.

libc++

La libc++ de LLVM es la biblioteca estándar de C++ que usa el SO Android desde la versión Lollipop, y a partir de NDK  r18, es la única STL disponible en el NDK.

La biblioteca compartida para libc++ es libc++_shared.so y la estática es libc++_static.a.

libc++ cuenta con la licencia de tipo BSD de la Universidad de Illinois y la licencia de MIT. Para obtener más información, consulta el archivo de licencia.

sistema

El tiempo de ejecución del sistema hace referencia a /system/lib/libstdc++.so. Esta biblioteca no se debe confundir con la biblioteca libstdc++ completa de GNU. En Android, libstdc++ solo cuenta con los operadores new y delete. Usa libc++ para una biblioteca estándar C++ completa.

El tiempo de ejecución C++ del sistema proporciona compatibilidad con la ABI básica del tiempo de ejecución C++. Básicamente, esta biblioteca proporciona los operadores new y delete. A diferencia de las otras opciones disponibles en el NDK, no se admite el manejo de excepciones o RTTI.

No se admite una biblioteca estándar aparte de los wrappers de C++ para los encabezados de la biblioteca C, como <cstdio>. Si quieres una STL, debes usar una de las otras opciones que se presentan en esta página.

ninguna

También tienes la opción de no elegir ninguna STL. En ese caso, no habrá requisitos de vinculación ni de licencias. No hay encabezados C++ estándar disponibles.

Cómo seleccionar un tiempo de ejecución C++

Si usas CMake, puedes especificar un tiempo de ejecución en la Tabla 1 con la variable ANDROID_STL en el archivo build.gradle del nivel del módulo. Para obtener más información, consulta Cómo usar las variables de CMake.

Si usas ndk-build, puedes especificar un tiempo de ejecución en la Tabla 1 con la variable APP_STL en el archivo Application.mk. por ejemplo:

APP_STL := c++_shared
    

Puedes seleccionar solo un tiempo de ejecución para tu app y solo puedes hacerlo en Application.mk.

Cuando uses una Cadena de herramientas independiente, esta usará la STL compartida de forma predeterminada. Para usar una variante estática, agrega -static-libstdc++ a los indicadores del vinculador.

Consideraciones importantes

Tiempos de ejecución estáticos

Si todo el código nativo de la app está contenido en una única biblioteca compartida, te recomendamos usar el tiempo de ejecución estático. De esta manera, el vinculador podrá intercalar y recortar la máxima cantidad de código sin utilizar, de modo que el resultado sea una app lo más pequeña y optimizada posible. Asimismo, evita los errores de PackageManager y del vinculador dinámico en las versiones anteriores de Android que dificultan el manejo de múltiples bibliotecas compartidas y facilitan la aparición de errores.

Dicho esto, en C++, no es seguro definir más de una copia de la misma función o del mismo objeto en un único programa. Este es un aspecto de la Regla de definición única (ODR) presente en el lenguaje C++ estándar.

Cuando se usa un tiempo de ejecución estático (y, en general, bibliotecas estáticas), esta regla puede infringirse por accidente fácilmente. Por ejemplo, la siguiente app infringe esta regla:

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

En este caso, la STL (incluidos los constructores estáticos y datos globales) está presente en ambas bibliotecas. El comportamiento de tiempo de ejecución de esta app no está definido, por lo que las fallas son muy frecuentes en la práctica. Entre otros posibles problemas se incluyen los siguientes:

  • Memoria asignada a una biblioteca, y liberada en la otra, lo cual provoca fugas de memoria o daños en el montón.
  • Excepciones que surgen en libfoo.so, no se detectan en libbar.so y provocan fallas en tu app.
  • Mal funcionamiento del almacenamiento en búfer de std::cout.

Más allá de los problemas de comportamiento, la vinculación del tiempo de ejecución estático con múltiples bibliotecas duplica el código en cada biblioteca compartida, lo que incrementa el tamaño de la app.

En general, solo puedes usar una variante estática del tiempo de ejecución C++ si tienes solo una biblioteca compartida en la app.

Tiempos de ejecución compartidos

Si tu app incluye varias bibliotecas compartidas, debes usar libc++_shared.so.

En Android, la libc++ que usa el NDK no forma parte del SO. De este modo, los usuarios del NDK acceden a las funciones de libc++ y correcciones de errores más recientes incluso cuando se orienta a las versiones más antiguas de Android. La contrapartida es que si usas libc++_shared.so, se deberá incluir en el APK. Si compilas tu app con Gradle, entonces se controla automáticamente.

Las versiones anteriores de Android tenían errores en PackageManager y en el vinculador dinámico que provocaban que la instalación, actualización y carga de bibliotecas nativas no fueran procesos confiables. En especial, si tu app se orienta a una versión de Android anterior a Android 4.3 (nivel de API 18 de Android) y usas libc++_shared.so, debes cargar la biblioteca compartida antes de cargar cualquier otra que dependa de ella.

El proyecto ReLinker ofrece soluciones alternativas para todos los problemas de carga de bibliotecas nativas conocidos, y, por lo general, suele ser una mejor opción que escribir tus propias soluciones.

Una STL por app

Históricamente, el NDK admitía libstdc++ de GNU y STLport, además de libc++. Si tu app depende de bibliotecas compiladas previamente en un NDK distinto del que se utilizó para compilar tu app, deberás asegurarte de que sea de manera compatible.

Una aplicación no debe usar más de un tiempo de ejecución C++. Las distintas STL no son compatibles entre ellas. Como ejemplo, el diseño de std::string en libc++ no es el mismo que en gnustl. El código escrito en una STL no podrá usar objetos escritos en otro lenguaje. Este es solo un ejemplo; hay muchas incompatibilidades.

Esta regla se extiende más allá de tu código. Todas tus dependencias deben usar la misma STL que seleccionaste. Si necesitas una dependencia de código cerrado de terceros que usa la STL, pero que no proporciona una biblioteca para cada STL, no podrás elegir la STL. Deberás usar la misma que la de tu dependencia.

Es posible que dependas de dos bibliotecas que son incompatibles entre sí. En esta situación, las únicas soluciones son prescindir de una de las dependencias o solicitar a quien las mantiene que proporcione una biblioteca compilada en otra STL.

Excepciones de C++

libc++ admite excepciones de C++, pero están inhabilitadas de forma predeterminada en ndk-build, porque, históricamente, las excepciones de C++ no estaban disponibles en el NDK. La cadena de herramientas independientes y de CMake tienen excepciones de C++ habilitadas de forma predeterminada.

Para habilitar excepciones en toda la app en ndk-build, agrega la siguiente línea en el archivo Application.mk:

APP_CPPFLAGS := -fexceptions
    

Si quieres activar excepciones para un solo módulo ndk-build, agrega la siguiente línea al módulo específico en su archivo Android.mk:

LOCAL_CPP_FEATURES := exceptions
    

Como alternativa, puedes usar lo siguiente:

LOCAL_CPPFLAGS := -fexceptions
    

RTTI

Al igual que ocurre con las excepciones, libc++ admite RTTI, pero está inhabilitado de forma predeterminada en ndk-build. CMake y los conjuntos de herramientas independientes tienen RTTI habilitado de forma predeterminada.

Para habilitar RTTI en toda la app en ndk-build, agrega la siguiente línea a tu archivo Application.mk:

APP_CPPFLAGS := -frtti
    

Para habilitar RTTI en un solo módulo ndk-build, agrega la siguiente línea al módulo dado en Android.mk:

LOCAL_CPP_FEATURES := rtti
    

Como alternativa, puedes usar lo siguiente:

LOCAL_CPPFLAGS := -frtti