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

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

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

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

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

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

libc++

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

libc++ の共有ライブラリは libc++_shared.so で、静的ライブラリは libc++_static.a です。

libc++ は、イリノイ大学の「BSD に類似した」ライセンスと MIT ライセンスというデュアルライセンスの下で使用が許諾されます。詳細については、ライセンス ファイルをご覧ください。

system

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

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

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

none

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

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

CMake を使用している場合は、モジュール レベルの build.gradle ファイルの ANDROID_STL 変数で、表 1 に記載したランタイムの 1 つを指定できます。詳細については、CMake 変数を使用するをご覧ください。

ndk-build を使用している場合は、Application.mk ファイルの APP_STL 変数で、表 1 に記載したランタイムの 1 つを指定できます。たとえば、次のようになります。

APP_STL := c++_shared
    

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

スタンドアロン ツールチェーンを使用している場合、デフォルトで共有 STL が使用されます。静的バリアントを使用するには、リンカーフラグに -static-libstdc++ を追加します。

重要な注意事項

静的ランタイム

アプリのすべてのネイティブ コードが単一の共有ライブラリ内にある場合、静的ランタイムを使用することをおすすめします。これにより、リンカーは、できる限り多くの未使用コードをインライン化し、取り除くことができるため、アプリの最適化、小型化を推進できます。また、旧バージョンの 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 を使用する場合は、APK 内に含める必要があります。Gradle を使用してアプリをビルドしている場合、この処理は自動的に行われます。

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

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

1 つのアプリに 1 つの STL

NDK はこれまで、libc++ のほかに GNU libstdc++ と STLport をサポートしてきました。アプリが、アプリのビルドに使用した NDK とは異なる 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