Compatibilidad de la biblioteca C++

El NDK admite varias 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 moderna con C++
system new y delete. (Dejó de estar disponible en r18)
none Sin encabezados, C++ limitado.

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

libc++

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

CMake usa la versión predeterminada de Clang C++ de forma predeterminada (actualmente C++14), por lo que tendrás que configurar el CMAKE_CXX_STANDARD estándar en el valor adecuado de tu archivo CMakeLists.txt para usarlo con C++17 o funciones posteriores. Para obtener más detalles, consulta la documentación de CMAKE_CXX_STANDARD de CMake.

ndk-build también usa Clang de forma predeterminada, por lo que los usuarios de ndk-build deben usar APP_CPPFLAGS para agregar -std=c++17 o lo que deseen.

La biblioteca compartida de libc++ es libc++_shared.so, y la biblioteca estática es libc++_static.a. En casos típicos, el sistema de compilación se encargará de usar y empaquetar estas bibliotecas según sea necesario para el usuario. Para casos atípicos o cuando implementas tu propio sistema de compilación, consulta la Guía para encargados de mantener el sistema de compilación o la guía para usar otros sistemas de compilación.

El proyecto LLVM está sujeto a la licencia Apache v2.0 con excepciones LLVM. Para obtener más información, consulta el archivo de licencia.

system

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

CMake

El valor predeterminado de CMake es c++_static.

Puedes especificar c++_shared, c++_static, none o system con la variable ANDROID_STL en el archivo build.gradle a nivel del módulo. Para obtener más información, consulta la documentación de ANDROID_STL en CMake.

ndk-build

El valor predeterminado para ndk-build es none.

Puedes especificar c++_shared, c++_static, none o system con la variable APP_STL en el archivo Application.mk. Por ejemplo:

APP_STL := c++_shared

ndk-build solo te permite seleccionar un tiempo de ejecución para tu app y solo puede hacerlo en Application.mk.

El uso de clang directamente

Si usas clang directamente en tu propio sistema de compilación, clang++ usará c++_shared 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 use el nombre "libstdc ++" por razones históricas, esto también es correcto 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 es la misma que 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 app. 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 (nivel de API 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 diferente 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 código cerrado 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 aplicación 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 aplicación 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 su Android.mk:

LOCAL_CPP_FEATURES := rtti

Como alternativa, puedes usar lo siguiente:

LOCAL_CPPFLAGS := -frtti