C++ ライブラリ サポート

NDK は、さまざまな C++ ランタイム ライブラリをサポートしています。このドキュメントでは、各ライブラリに関する情報、トレードオフ、使用方法について説明します。

C++ ランタイム ライブラリ

表 1. NDK C++ ランタイムと機能

名前 機能
libc++ 最新の C++ サポート。
system newdelete(r18 でサポート終了)。
none ヘッダーなし。限定的な C++。

libc++ は、静的ライブラリとしても共有ライブラリとしても利用することができます。

libc++

LLVM の libc++ は、Lollipop 以降の Android OS で使用されている C++ 標準ライブラリであり、NDK r18 以降は NDK 内で唯一利用可能な STL です。

CMake は、clang のデフォルトである C++ のバージョン(現在は C++14)にデフォルトで設定されています。そのため、C++17 以降の機能を使用するには、CMakeLists.txt ファイルで標準の CMAKE_CXX_STANDARD を適切な値に設定する必要があります。詳細については、CMake の CMAKE_CXX_STANDARD のドキュメントをご覧ください。

また、ndk-build はデフォルトで clang によって決定されるため、ndk-build のユーザーが -std=c++17 などを追加する場合は、APP_CPPFLAGS を使用する必要があります。

libc++ の共有ライブラリは libc++_shared.so で、静的ライブラリは libc++_static.a です。一般的に、ユーザーの必要に応じてこれらのライブラリの使用やパッケージ化はビルドシステムが処理します。特殊なケースの場合、または独自のビルドシステムを実装する場合は、ビルドシステム メンテナンス ガイドまたは他のビルドシステムの使用に関するガイドをご覧ください。

LLVM プロジェクトには Apache License v2.0 が適用され、LLVM 例外があります。詳細については、ライセンス ファイルをご覧ください。

system

system ランタイムは /system/lib/libstdc++.so を参照します。このライブラリを、GNU の全機能を備えた libstdc++ と混同しないように注意してください。Android の場合、libstdc++ は newdelete だけです。フル機能の C++ 標準ライブラリには libc++ を使用します。

system C++ ランタイムは、基本的な C++ ランタイム ABI をサポートしています。 基本的に、このライブラリでは newdelete を使用できます。NDK 内で利用できる他のオプションとは異なり、例外処理や RTTI のサポートはありません。

<cstdio> など、C ライブラリ ヘッダーの C++ ラッパー以外の標準ライブラリはサポートされていません。STL を必要とする場合、このページに記載されている別のオプションを使用することをおすすめします。

なし

STL を使用しないオプションもあります。この場合、リンク要件やライセンス要件は適用されません。C++ 標準ヘッダーは利用できなくなります。

C++ ランタイムを選択する

CMake

CMake のデフォルトは c++_static です。

モジュール レベルの build.gradle ファイルで ANDROID_STL 変数を使用して、c++_sharedc++_staticnonesystem を指定できます。詳細については、CMake の ANDROID_STL に関するドキュメントをご覧ください。

ndk-build

ndk-build のデフォルトは none です。

Application.mk ファイルの APP_STL 変数を使用して、c++_sharedc++_staticnonesystem を指定できます。次に例を示します。

APP_STL := c++_shared

ndk-build では、アプリに対して選択できるランタイムは 1 つだけに限られており、Application.mk 内で指定する必要があります。

clang を直接使用する

独自のビルドシステムで clang を直接使用している場合、clang++ はデフォルトで c++_shared を使用します。静的バリアントを使用するには、リンカーフラグに -static-libstdc++ を追加します。このオプションは歴史的な理由から「libstdc++」という名前を使用しますが、「libc ++」でも同様です。

重要な注意事項

静的ランタイム

アプリのすべてのネイティブ コードが単一の共有ライブラリ内にある場合、静的ランタイムを使用することをおすすめします。これにより、リンカーは、できる限り多くの未使用コードをインライン化し、取り除くことができるため、アプリの最適化、小型化を推進できます。また、旧バージョンの Android では、Package Manager とダイナミック リンカーにバグがあり、複数の共有ライブラリを処理することが難しく、エラーが発生しやすくなっていましたが、このようなバグも回避することができます。

ただし、それでも C++ では、同一の関数やオブジェクトの複数のコピーを、単一のプログラム内で定義することは安全ではありません。この点が、C++ 標準に「単一定義ルール」が存在する 1 つの理由となっています。

静的ランタイム(一般的には静的ライブラリ)を使用する場合、意図せずこのルールに違反することがよくあります。たとえば、次のアプリはこのルールに違反しています。

# 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++ ランタイムの静的バリアントを使用できるのは、アプリ内に共有ライブラリが 1 つだけの場合に限られます。

共有ランタイム

アプリに複数の共有ライブラリが含まれている場合は、libc++_shared.so を使用する必要があります。

Android では、NDK が使用する libc++ は、OS に組み込まれたものと同じではありません。これにより、NDK ユーザーは、旧バージョンの Android をターゲットにした場合でも、最新の libc++ 機能とバグ修正を利用することができます。そのトレードオフとして、libc++_shared.so を使用する場合はアプリに含める必要があります。Gradle を使用してアプリをビルドする場合、この処理は自動的に行われます。

旧バージョンの Android では、Package Manager とダイナミック リンカーにバグがあり、ネイティブ ライブラリのインストール、更新、読み込みの信頼性が低下していました。特に、Android 4.3(Android API レベル 18)より前の Android バージョンをターゲットにしているアプリで、libc++_shared.so を使用している場合、必ず先に共有ライブラリを読み込んでから、この共有ライブラリに依存している他のライブラリを読み込む必要があります。

ReLinker プロジェクトでは、ネイティブ ライブラリの読み込みに関するすべての既知の問題に対して、対応策を用意しています。通常は、独自の対応策を採用するよりも、用意されている対応策を利用することをおすすめします。

1 つのアプリに 1 つの STL

従来、NDK は libc++ に加えて GNU libstdc++ と STLport をサポートしていました。アプリが、アプリのビルドに使用したものとは異なる NDK に対してビルドされたビルド済みライブラリに依存している場合は、互換性のある方法でそれを実行する必要があります。

1 つのアプリで複数の C++ ランタイムを使用しないでください。それぞれの STL 間には互換性がありません。たとえば、libc++ と gnustl では std::string のレイアウトが異なります。ある STL に基づいて記述されたコードは、別の STL に基づいて記述されたオブジェクトを使用することはできません。これは単なる一例であり、ほかにも、さまざまな面で互換性がありません。

このルールはコード以外にも適用されます。すべての依存関係に対して、同一の選択済み STL を使用する必要があります。利用しているクローズド ソースのサードパーティ依存関係が、STL を使用していて、かつ、STL ごとにライブラリを提供していない場合、STL を選択することはできません。自身のコード内の依存関係と同じ STL を使用する必要があります。

相互に互換性のない 2 つのライブラリに依存する場合があります。この状況の解決策としては、いずれかの依存関係を削除するか、他方の STL に基づいてビルドされたライブラリを提供するよう管理者に依頼する必要があります。

C++ 例外

C++ 例外は libc++ ではサポートされますが、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

例外と同様、RTTI は libc++ でサポートされていますが、ndk-build では、デフォルトで無効になっています。CMake とスタンドアロン ツールチェーンでは、デフォルトで RTTI が有効になっています。

ndk-build 内のアプリ全体で RTTI を有効にするには、以下の行を Application.mk ファイルに追加します。

APP_CPPFLAGS := -frtti

単一の ndk-build モジュールで RTTI を有効にするには、以下の行を Android.mk 内の対象モジュールに追加します。

LOCAL_CPP_FEATURES := rtti

あるいは、次の行を使用することもできます。

LOCAL_CPPFLAGS := -frtti