C++ 程式庫支援

NDK 支援多個 C++ 執行階段程式庫。本文件提供有關這些程式庫的相關資訊、利弊間的權衡和使用方式。

C++ 執行階段程式庫

表 1. NDK C++ 執行階段與功能。

名稱 功能
libc++ 現代 C++ 支援。
system newdelete (在 r18 中已淘汰)。
none 沒有標頭,有限的 C++。

libc++ 同時提供靜態和共享的程式庫。

libc++

LLVM 的 libc++ 是 C++ 標準程式庫,亦是 Android 作業系統自 Lollipop 以來便使用的程式庫;NDK r18 則是 NDK 中唯一可用的 STL。

根據預設,CMake 設為 C++ clang 的預設版本 (目前為 C++14),因此您必須將 CMakeLists.txt 檔案中的標準 CMAKE_CXX_STANDARD 設為適當的值,才能使用 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 授權 2.0 版約束。詳情請參閱授權檔案

system

系統執行階段參照 /system/lib/libstdc++.so。請勿將這個程式庫與 GNU 功能完整的 libstdc++ 弄混。在 Android 上,libstdc++ 只是 newdelete。使用 libc++ 建立功能完整的 C++ 標準程式庫。

系統 C++ 執行階段支援基本的 C++ 執行階段 ABI。基本上,這個程式庫會提供 newdelete。相較於 NDK 中所提供的其他選項,這個程式庫不支援例外狀況處理或 RTTI。

除了 <cstdio> 這類 C 程式庫標頭的 C++ 包裝函式之外,也不支援標準程式庫。如果需要 STL,請使用本頁面列出的其他選項。

none

您也可以選擇不使用 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 只允許為您的應用程式選取一個執行階段,而且只能在 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 的 libc++。因此,即使已將舊版 Android 指定為目標平台,NDK 使用者仍可存取最新的 libc++ 功能和錯誤修正,而需要權衡的是,如果您使用 libc++_shared.so,就必須將其納入應用程式中;如果使用 Gradle 建構應用程式,這個步驟就會自動完成。

舊版 Android 的 PackageManager 和動態連結器中有程式上的錯誤,這導致安裝、更新及載入原生資料庫並不安全可靠。具體來說,如果您的應用程式指定為 Android 4.3 (Android API 級別 18) 之前的 Android 版本,而且您使用 libc++_shared.so,您就必須先載入共享程式庫,再讓任何其他程式庫依附該共享程式庫。

ReLinker 專案提供所有已知原生資料庫載入問題的暫時解決方法,而且比起自行編寫暫時解決方法,這通常是更好的選擇。

每個應用程式一個 STL

過去,NDK 支援 GNU libstdc++ 和 STLport 以及 libc++。如果應用程式依附的預先建構程式庫是根據 NDK 建構的程式庫,與建構應用程式時使用的 NDK 不同,則必須確保該程式庫採用相容的方式執行。

單一應用程式不得使用多個 C++ 執行階段。不同 STL 互相容。例如,libc++ 中的 std::string 佈局與 gnustl 中的佈局不同。根據某個 STL 編寫的程式碼無法使用以其他 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

與例外狀況相同,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