C++ 라이브러리 지원

NDK는 여러 C++ 런타임 라이브러리를 지원합니다. 이 문서는 이러한 라이브러리, 관련 절충점, 사용 방법에 관한 정보를 제공합니다.

C++ 런타임 라이브러리

표 1. NDK C++ 런타임 및 기능

이름 기능
libc++ 최신 C++ 지원
시스템 newdelete (r18에서 지원 중단됨)
없음 헤더 없음. 제한된 C++

libc++는 정적 라이브러리와 공유 라이브러리 두 가지로 사용할 수 있습니다.

libc++

LLVM의 libc++는 Lollipop 이후 Android OS에서 사용되는 C++ 표준 라이브러리이며 NDK r18부터는 NDK에서 사용할 수 있는 유일한 STL입니다.

CMake의 기본값은 C++ clang 버전(현재 C++14)의 기본값이므로 표준 CMAKE_CXX_STANDARDCMakeLists.txt 파일의 적절한 값으로 설정하여 C++17 이상의 기능을 사용해야 합니다. 자세한 내용은 CMake CMAKE_CXX_STANDARD 문서를 참고하세요.

또한 ndk-build는 기본적으로 clang에 결정을 맡기므로 ndk-build 사용자는 APP_CPPFLAGS를 사용하여 -std=c++17이나 이외에 원하는 것을 추가해야 합니다.

libc++의 공유 라이브러리는 libc++_shared.so이며 정적 라이브러리는 libc++_static.a입니다. 일반적으로 빌드 시스템은 사용자에게 필요한 경우 이러한 라이브러리의 사용 및 패키징을 처리합니다. 특수한 상황이거나 자체 빌드 시스템을 구현한다면 빌드 시스템 담당자 가이드 또는 다른 빌드 시스템 사용에 관한 가이드를 참고하세요.

LLVM 프로젝트는 LLVM 예외가 있는 Apache 라이선스 v2.0에 속합니다. 자세한 내용은 라이선스 파일을 참고하세요.

시스템

시스템 런타임은 /system/lib/libstdc++.so를 참조합니다. 이 라이브러리를 GNU의 모든 기능을 갖춘 libstdc++와 혼동해서는 안 됩니다. Android에서 libstdc++는 newdelete뿐입니다. 모든 기능을 갖춘 C++ 표준 라이브러리에는 libc++를 사용하세요.

시스템 C++ 런타임은 기본 C++ 런타임 ABI를 지원합니다. 기본적으로 이 라이브러리에서는 newdelete를 제공합니다. NDK에서 사용할 수 있는 다른 옵션과 대조적으로 예외 처리나 RTTI는 지원되지 않습니다.

<cstdio>와 같은 C 라이브러리 헤더용 C++ 래퍼 외에는 표준 라이브러리 지원이 없습니다. 따라서 STL을 원한다면 이 페이지에 제시된 다른 옵션 중 하나를 사용해야 합니다.

없음

STL이 없는 옵션도 있습니다. 이런 상황에서는 링크 또는 라이선스 요구사항이 없으며 이용할 수 있는 C++ 표준 헤더도 없습니다.

C++ 런타임 선택

CMake

CMake의 기본값은 c++_static입니다.

모듈 수준 build.gradle 파일에서 ANDROID_STL 변수를 사용하여 c++_sharedc++_static, none, system을 지정할 수 있습니다. 자세한 내용은 CMake의 ANDROID_STL 문서를 참고하세요.

ndk-build

ndk-build의 기본값은 none입니다.

Application.mk 파일에서 APP_STL 변수를 사용하여 c++_sharedc++_static, none, system을 지정할 수 있습니다. 예:

APP_STL := c++_shared

ndk-build를 사용하면 앱에 런타임을 하나만 선택할 수 있고 Application.mk에서만 선택할 수 있습니다.

clang을 직접 사용

자체 빌드 시스템에서 clang을 직접 사용한다면 clang++가 기본적으로 c++_shared를 사용합니다. 정적 변형을 사용하려면 링커 플래그에 -static-libstdc++를 추가하세요. 이 옵션은 몇 가지 이유 때문에 'libstdc++'라는 이름을 사용하지만 libc++도 사용할 수 있습니다.

중요한 고려사항

정적 런타임

애플리케이션의 모든 네이티브 코드가 단일 공유 라이브러리에 포함되어 있으면 정적 런타임을 사용하는 것이 좋습니다. 정적 런타임을 사용하면 링커가 가능한 한 사용되지 않은 코드를 인라인 처리하거나 제거하여 가장 작고 최적화된 애플리케이션을 만들 수 있습니다. 또한 다중 공유 라이브러리 처리를 어렵게 만들고 오류를 자주 발생시키던 이전 Android 버전의 PackageManager 및 동적 링커 버그를 피할 수 있습니다.

그렇지만 C++에서 단일 프로그램에 동일한 함수나 객체를 둘 이상 정의하는 것은 안전하지 않습니다. 이는 C++ 표준에 제시된 단일 정의 규칙의 한 측면입니다.

정적 런타임(및 일반적으로 정적 라이브러리)을 사용할 때 이 규칙을 무심결에 간과하기 쉽습니다. 예를 들어 다음 애플리케이션은 이 규칙에 위배됩니다.

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

이런 상황에서는 전역 데이터 및 정적 생성자를 비롯한 STL이 양쪽 라이브러리에 표시됩니다. 이 애플리케이션의 런타임 동작은 정의되지 않았으며 실제로는 비정상 종료가 매우 흔하게 일어납니다. 그 밖에 발생할 수 있는 문제는 다음과 같습니다.

  • 한 라이브러리에 할당된 메모리가 다른 라이브러리에서 해제되어 메모리 누수 또는 힙 손상이 발생합니다.
  • libfoo.so에서 발생한 예외가 libbar.so에서 포착되지 않아 앱이 비정상 종료됩니다.
  • std::cout의 버퍼링이 제대로 작동하지 않습니다.

관련 동작 문제 외에도 정적 런타임을 여러 라이브러리에 연결하면 각 공유 라이브러리의 코드가 중복되어 애플리케이션의 크기가 커집니다.

일반적으로 애플리케이션에 공유 라이브러리가 한 개만 있으면 C++ 런타임의 정적 변형만 사용할 수 있습니다.

공유 런타임

애플리케이션에 다중 공유 라이브러리가 포함되었다면 libc++_shared.so를 사용해야 합니다.

Android에서는 NDK에서 사용하는 libc++는 OS의 일부와 동일하지 않습니다. 따라서 NDK 사용자는 이전 버전의 Android를 타겟팅할 때도 최신 libc++ 기능 및 버그 수정사항을 이용할 수 있습니다. 단, libc++_shared.so를 사용하려면 앱에 포함해야 합니다. Gradle로 애플리케이션을 빌드한다면 이 작업이 자동으로 처리됩니다.

이전 버전의 Android에는 PackageManager 및 동적 링커에 네이티브 라이브러리의 설치, 업데이트, 로드 안정성을 저해하는 버그가 있습니다. 특히 앱이 Android 4.3(Android API 수준 18) 이전 버전의 Android를 타겟팅하고 libc++_shared.so를 사용한다면 공유 라이브러리를 먼저 로드한 후 종속되는 다른 라이브러리를 로드해야 합니다.

ReLinker 프로젝트에서는 알려진 모든 네이티브 라이브러리 로드 문제 해결 방법을 제공하므로 이 프로젝트를 선택하는 것이 자체 해결 방법을 작성하는 것보다 더 낫습니다.

앱당 하나의 STL

예전부터 NDK는 libc++ 외에 GNU libstdc++, STLport도 지원했습니다. 애플리케이션이 애플리케이션을 빌드하는 데 사용한 것과 다른 NDK에 대해 미리 빌드된 라이브러리를 사용한다면 애플리케이션이 호환 가능한 방식으로 라이브러리를 사용하도록 해야 합니다.

애플리케이션에서 둘 이상의 C++ 런타임을 사용하면 안 됩니다. 다양한 STL은 서로 호환되지 않습니다. 예를 들어 libc++의 std::string 레이아웃은 gnustl의 레이아웃과 동일하지 않습니다. 한 STL에서 작성된 코드는 다른 STL에서 작성된 객체를 사용할 수 없습니다. 이런 상황은 하나의 예에 불과하며 비호환성 사례는 수도 없이 많습니다.

이 규칙은 작성한 코드에만 적용되는 것이 아닙니다. 모든 종속 항목은 선택된 것과 동일한 STL을 사용해야 합니다. STL을 사용하고 STL별 라이브러리를 제공하지 않는 비공개 소스 타사 종속 항목에 의존한다면 STL 선택권이 없습니다. 즉, 종속 항목과 동일한 STL을 사용해야 합니다.

서로 호환되지 않는 두 개의 라이브러리에 의존하는 경우도 있을 수 있습니다. 이 상황에서 유일한 해결책은 종속 항목 중 하나를 제외하거나 운영자에게 다른 STL에서 빌드된 라이브러리를 제공하도록 요청하는 것입니다.

C++ 예외

libc++에서 C++ 예외를 지원하지만 ndk-build에서는 기본적으로 이러한 예외가 사용 중지되어 있습니다. 이전부터 NDK에서는 C++ 예외를 사용할 수 없었기 때문입니다. CMake 및 독립 실행형 도구 모음에는 기본적으로 C++ 예외가 사용 설정되어 있습니다.

ndk-build의 전체 애플리케이션에서 예외를 사용 설정하려면 Application.mk 파일에 다음 행을 추가하세요.

APP_CPPFLAGS := -fexceptions

단일 ndk-build 모듈에 예외를 사용 설정하려면 Android.mk의 지정된 모듈에 다음 행을 추가하세요.

LOCAL_CPP_FEATURES := exceptions

또는 다음을 사용할 수 있습니다.

LOCAL_CPPFLAGS := -fexceptions

RTTI

예외와 마찬가지로 libc++에서 RTTI를 지원하지만 ndk-build에서는 기본적으로 RTTI 사용이 중지되어 있습니다. CMake 및 독립 실행형 도구 모음에는 RTTI가 기본적으로 사용 설정되어 있습니다.

ndk-build의 전체 애플리케이션에서 RTTI를 사용 설정하려면 Application.mk 파일에 다음 행을 추가하세요.

APP_CPPFLAGS := -frtti

단일 ndk-build 모듈에 RTTI를 사용 설정하려면 Android.mk의 지정된 모듈에 다음 행을 추가하세요.

LOCAL_CPP_FEATURES := rtti

또는 다음을 사용할 수 있습니다.

LOCAL_CPPFLAGS := -frtti