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).
nadie 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 de libc++ es libc++_shared.so y la biblioteca 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 entorno de ejecución del sistema hace referencia a /system/lib/libstdc++.so. Esta biblioteca no debe confundirse con la libstdc++ que tiene todas las funciones de GNU. En Android, libstdc++ es solo new y delete. Usa libc++ para una biblioteca estándar C++ completa.

El entorno de ejecución de C++ del sistema ofrece compatibilidad con la ABI básica del entorno de ejecución de C++. Básicamente, esta biblioteca proporciona 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 de 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 de 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 la variante estática, agrega -static-libstdc++ a las marcas del vinculador. Ten en cuenta que, aunque la opción usa el nombre "libstdc ++", también es correcta para libc ++.

Consideraciones importantes

Tiempos de ejecución estáticos

Si todo el código nativo de la aplicación 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 aplicación 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 aplicación 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 los datos globales) está presente en ambas bibliotecas. El comportamiento de tiempo de ejecución de esta aplicación 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 que provoca fugas de memoria o daños en la pila.
  • Las excepciones que surgen en libfoo.so no se detectan en libbar.so y provocan fallas en tu app.
  • El almacenamiento en búfer de std::cout no funciona correctamente.

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 aplicación.

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

Tiempos de ejecución compartidos

Si tu aplicación incluye varias bibliotecas compartidas, debes usar libc++_shared.so.

En Android, la libc++ que usa el NDK no forma parte del SO. Esto brinda a los usuarios del NDK acceso a las últimas funciones y correcciones de errores de libc++, incluso cuando se orienta a las versiones más antiguas de Android. La desventaja es que si usas libc++_shared.so, debes incluirlo en tu APK. Si compilas tu aplicación con Gradle, 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 particular, si tu app se orienta a una versión de Android anterior a Android 4.3 (API nivel 18) y usas libc++_shared.so, debes cargar la biblioteca compartida antes que cualquier otra biblioteca 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++ y STLport de GNU además de libc++. Si tu aplicación depende de bibliotecas compiladas previamente en un NDK distinto del que se usó para compilar tu aplicación, deberás asegurarte de que lo haga de manera compatible.

Una aplicación no debe usar más de un entorno 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 fuente cerrada que use la STL, pero que no proporciona una biblioteca para cada STL, no podrás elegir la STL. Debes usar la misma STL que 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. Esto se debe a que, históricamente, las excepciones de C++ no estaban disponibles en el NDK. CMake y las cadenas de herramientas independientes 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 habilitar 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 las cadenas 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