Heresy 目前開發跨平台的 C++ 程式的時候,主要是透過 Windows 10 的「Bash on Ubuntu on Windows」這個 Windows Subsystem for Linux(WSL)來進行操作的。
在微軟剛貼出的時候,他的 Ubunutu 版本還是 14,而後來在微軟推出 Creators Update (1703) 的時候,也把 Ubuntu 的版本升級到 16.04 了~而在 Ubuntu 系統版本升級的同時,系統預設使用的編譯器也由 g++ 4.x 更新到 g++ 5.x 了。
而也因為這樣,當 Heresy 要做 Linux 環境的建置測試的時候,也碰到了一些問題,這邊就稍微紀錄一下了。
首先,會有問題的主要原因,是 GCC5.1 在發布的同時,也推出了新的 libstdc++;而這個 C++ 的標準函式庫為了符合 C++11 的標準,重新實作了 std::string 和 std::list;而這兩個類別由於遵循了新的規範(禁止 Copy-On-Write),所以變得和舊版的函式庫不相容。
為了避免混亂,gcc 5.1 修改了對應的 ABI(Application binary interface),像是 std::list<int> 實際上會是定義成 std::__cxx11::list<int> 的形式;基本上,就是有部分標準函式庫的東西的 namespace 會從 std 變成 std::__cxx11 了。
而這樣做的結果,就是導致使用新的 g++ 和新的 libstdc++ 建置程式的時候,如果要用到使用舊版 libstdc++ 的第三方函式庫的時候,就會因為實作的 namespace 的不同,而在連結階段出現找不到對應的函式的問題了!對應的錯誤訊息基本上會是「undefined reference」。
為了避免麻煩,他們也有提供一個 macro「_GLIBCXX_USE_CXX11_ABI」,讓開發者可以決定要使用哪種 ABI。比如果想要連結舊版 ABI 的函式庫的時候,只要在編譯的時候,在編譯參數加上「-D_GLIBCXX_USE_CXX11_ABI=0」就可以了~
這部分的詳細說明,可以參考 GNU 的官方文件《Dual ABI》。
雖然 GNU 有提供一個切回舊版 ABI 的方法,來避免和既有的函式庫不相容的問題,不過,事情還沒結束…
Heresy 這邊踩到的問題,變成是 Ubuntu APT 套件管理系統上的套件,感覺上並沒有統一的更新,結過變成有的是使用新的 ABI、有的是使用舊的 ABI!
以 Heresy 自己碰到的情況來說,系統提供的 Boost C++ Libraries 目前還在 1.58,同時他使用的是新的 ABI 了;而同時,OpenCV 則是使用 2.4,使用的是新的 ABI。
在這樣的狀況下,如果要同時使用 Boost 和 OpenCV 會產生什麼問題呢?那就是不管怎麼設定,一定會有一個沒辦法連結到…
下面就是一個簡單的範例:
// Boost Header #include <boost/program_options.hpp> // OpenCV Header #include <opencv2/core/core.hpp> int main() { cv::Mat img; cv::putText(img, "x", cv::Point(0, 0), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(1)); boost::program_options::options_description bOptions("Test Options"); }
以上面這樣一個簡單,同時用到 OpenCV 和 boost::program_options 的程式來說,如果什麼都不管,直接用 g++ 編譯的話,應該會是類似下面的指令:
g++ b.cpp -lopencv_core -lboost_program_options
得到的結果,會是沒辦法正確連結到 boost::program_options 的部分函式。
/tmp/ccxd355d.o: In function `main':
b.cpp:(.text+0x138): undefined reference to `boost::program_options::options_description::options_description(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned int, unsigned int)'
collect2: error: ld returned 1 exit status
這邊的會出現沒辦法找到正確的參考(undefined reference)的原因,是在使用 g++ 5 編譯時,std::string 會變成 std::__cxx11::string,然後再進一步被轉換成上面 std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >。
但是透過 apt 取得、預先編譯好的 boost 裡面的 std::string 卻是被轉換成 std::basic_string<char, std::char_traits<char>, std::allocator<char> >,由於缺了一層「__cxx11」,所以使的函式介面變得不相同,無法使用。
基本上,如果有看到找不到正確的參考的後面,有 std::__cxx11 的話,很有可能就是因為要連結的函式庫是使用舊的 ABI 所造成的。
而這個時候,如果參考 GNU 的文件,在編譯時把 _GLIBCXX_USE_CXX11_ABI 定義成 0 呢?
在不修改程式碼的情況下,可以使用下面的編譯參數:
g++ b.cpp -lopencv_core -lboost_program_options -D_GLIBCXX_USE_CXX11_ABI=0
這樣編譯的結果,固然使用舊的 ABI 的 Boost 可以正確連結了,但是對應的,變成使用新的 ABI 的 OpenCV 連結不到了…
出現的錯誤會類似下面的樣子:
/tmp/ccaLNeUy.o: In function `main':
b.cpp:(.text+0xba): undefined reference to `cv::putText(cv::Mat&, std::string const&, cv::Point_<int>, int, double, cv::Scalar_<double>, int, int, bool)'
collect2: error: ld returned 1 exit status
這邊的問題和前面一樣,只是反過來而已。
至於解決方法呢?基本上,大概也只能選一個 ABI,然後重新編譯其他的函式庫了吧?
以 Heresy 這邊來說,基本上就是重新編譯 Boost C++ Libraries 了…
話說,為什麼 Ubuntu 的套件管理會搞到上面的套件不相容啊? ><