NDK 支援多個 C++ 執行階段程式庫。本文件提供有關這些程式庫的相關資訊、利弊間的權衡和使用方式。
C++ 執行階段程式庫
表 1. NDK C++ 執行階段與功能。
名稱 | 功能 |
---|---|
libc++ | 現代 C++ 支援。 |
system | new 和 delete (在 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++ 只是 new
和 delete
。使用 libc++ 建立功能完整的 C++ 標準程式庫。
系統 C++ 執行階段支援基本的 C++ 執行階段 ABI。基本上,這個程式庫會提供 new
和 delete
。相較於 NDK 中所提供的其他選項,這個程式庫不支援例外狀況處理或 RTTI。
除了 <cstdio>
這類 C 程式庫標頭的 C++ 包裝函式之外,也不支援標準程式庫。如果需要 STL,請使用本頁面列出的其他選項。
none
您也可以選擇不使用 STL。在這種情況下,便沒有任何連結或授權要求,亦沒有提供 C++ 標準標頭。
選取 C++ 執行階段
CMake
CMake 的預設值為 c++_static
。
您可以使用模組層級 build.gradle
檔案中的 ANDROID_STL
變數來指定 c++_shared
、c++_static
、none
或 system
。詳情請參閱 CMake 中的 ANDROID_STL 說明文件。
ndk-build
ndk-build 的預設值為 none
。
您可以使用 Application.mk 檔案中的 APP_STL
變數指定 c++_shared
、c++_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 的 libc++。因此,即使已將舊版 Android 指定為目標平台,NDK 使用者仍可存取最新的 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 不同於建構應用程式所用的 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