1. 程式人生 > >Ubuntu 16.04系統中GCC 7.3編譯器安裝方法及C++17標準測試示例

Ubuntu 16.04系統中GCC 7.3編譯器安裝方法及C++17標準測試示例

2017年底,C++17標準正式頒佈,該標準的最大貢獻是,提供了STL庫演算法的並行運算版本,對於我這種喜歡追求演算法效能的程式設計師而言,無疑是一個極大的福音。幸運地是,Linux系統標準編譯器GCC能完美地支援C++ 17標準,但需升級到7.0以上版本;不幸地是,Ubuntu 16.04版本自帶的GCC版本為5.4.0,可支援C++ 14標準,但基本不支援C++ 17標準。怎麼辦?那就從零開始,從GCC官方網站下載、安裝最新標準的編譯器吧。

一、下載GCC 7.3.0版本原始碼

進入GCC官方網站:https://gcc.gnu.org/,發現目前最新版本是7.3.0(2018年3月24日 )。本來想省點事,直接下載二進位制版本進行安裝,但開啟相關頁面後,發現居然沒有Ubuntu系統的二進位制版本,這不是赤裸裸地歧視嗎?話雖如此,但活人還能讓尿憋死?既然官網不提供,那我就自己下載原始碼編譯、安裝。
1


目前,GCC原始碼提供兩種下載方式:映象網站下載和SVN伺服器下載。目前,我只使用GIT版本控制工具,對於SVN這種跟不上趟的玩意,我根本不屑使用,還是從映象伺服器下載好了。開啟映象伺服器列表網頁一看:https://gcc.gnu.org/mirrors.html,居然沒有大中國,又是赤裸裸地歧視啊。看在老毛子數學和軟體演算法都很牛逼的份上,我就選他的映象伺服器吧:http://mirror.linux-ia64.org/gnu/gcc/,具體下載網址為:http://mirror.linux-ia64.org/gnu/gcc/releases/gcc-7.3.0/
2
3

二、編譯安裝GCC 7.3.0

2.1 解壓原始碼壓縮包

從映象伺服器下載GCC 7.3.0版原始碼壓縮包“gcc-7.3.0.tar.gz”後,首先將其放置於一個合適的目錄,進行解壓(我將“gcc-7.3.0.tar.gz”放置於目錄“/home/davidhopper/code/gcc”中),命令如下:

cd ~/code/gcc
tar zxvf gcc-7.3.0.tar.gz

結果就是所有原始碼檔案全部解壓到了目錄“/home/davidhopper/code/gcc/gcc-7.3.0”中。

2.2 建立構建資料夾

由於GCC官網的安裝文件強烈建議不要在原始碼檔案中進行編譯,而需另外建立一個構建資料夾(原文為:First, we highly recommend that GCC be built into a separate directory from the sources which does not reside within the source tree. This is how we generally build GCC; building where srcdir == objdir should still work, but doesn’t get extensive testing; building where objdir is a subdirectory of srcdir is unsupported.),因此,我在“/home/davidhopper/code/gcc”目錄中另行建立一個單獨的構建資料夾“gcc-7.3.0-build”,整個目錄結構如下圖所示:
3

2.3 下載依賴包

此時,我以為只要進入構建資料夾,然後執行如下配置命令就可以生成Makefile了,真是“too simple, sometimes naive”,世界哪有這麼美好?

cd ~/code/gcc/gcc-7.3.0-build
../gcc-7.3.0/configure

果不其然,上述命令產生的錯誤資訊如下,原來是缺少幾個依賴包:GMP 4.2+, MPFR 2.4.0+, MPC 0.8.0+,需要我們自己下載。

configure: error: Building GCC requires GMP 4.2+, MPFR 2.4.0+ and MPC 0.8.0+.
Try the --with-gmp, --with-mpfr and/or --with-mpc options to specify
their locations.  Source code for these libraries can be found at
their respective hosting sites as well as at
ftp://gcc.gnu.org/pub/gcc/infrastructure/.  See also
http://gcc.gnu.org/install/prerequisites.html for additional info.  If
you obtained GMP, MPFR and/or MPC from a vendor distribution package,
make sure that you have installed both the libraries and the header
files.  They may be located in separate packages.

4

下載方法有兩種:
第一種是笨辦法,老老實實地進入映象伺服器網址,然後依次下載:GMP 6.1.0, MPFR 3.1.4, MPC 1.0.3壓縮包(只要滿足GMP 4.2+, MPFR 2.4.0+, MPC 0.8.0+要求即可,並不一定是我所寫的這幾個版本),並將其解壓到“/home/davidhopper/code/gcc/gcc-7.3.0”目錄,同時建立其符號連結目錄。也就是說,在“/home/davidhopper/code/gcc/gcc-7.3.0”目錄中會多出三個子資料夾:gmp-6.1.0、mpfr-3.1.4、mpc-1.0.3以及其其符號連結目錄:gmp、mpfr、mpc。解壓及建立符號連結命令如下:

cd ~/code/gcc/gcc-7.3.0
tar zxvf gmp-6.1.0.tar.gz
tar zxvf mpfr-3.1.4.tar.gz
tar zxvf mpc-1.0.3.tar.gz
ln -s  gmp-6.1.0 gmp
ln -s  mpfr-3.1.4 mpfr
ln -s  mpc-1.0.3 mpc

第二種是省事的辦法,首先使用vi編輯器開啟依賴包下載指令碼檔案:contrib/download_prerequisites

cd ~/code/gcc/gcc-7.3.0
vi contrib/download_prerequisites

將該檔案裡的base_url='ftp://gcc.gnu.org/pub/gcc/infrastructure/'
替換為:base_url='http://mirror.linux-ia64.org/gnu/gcc/infrastructure/',即將不存在的伺服器地址替換為映象伺服器地址。接下來,執行如下命令自動下載並解壓依賴包:

bash contrib/download_prerequisites

如果提示如下資訊,則代表下載並解壓成功:

2018-03-24 21:01:37 URL:http://mirror.linux-ia64.org/gnu/gcc/infrastructure/gmp-6.1.0.tar.bz2 [2383840/2383840] -> "./gmp-6.1.0.tar.bz2" [1]
2018-03-24 21:01:46 URL:http://mirror.linux-ia64.org/gnu/gcc/infrastructure/mpfr-3.1.4.tar.bz2 [1279284/1279284] -> "./mpfr-3.1.4.tar.bz2" [1]
2018-03-24 21:01:51 URL:http://mirror.linux-ia64.org/gnu/gcc/infrastructure/mpc-1.0.3.tar.gz [669925/669925] -> "./mpc-1.0.3.tar.gz" [1]
2018-03-24 21:01:58 URL:http://mirror.linux-ia64.org/gnu/gcc/infrastructure/isl-0.16.1.tar.bz2 [1626446/1626446] -> "./isl-0.16.1.tar.bz2" [1]
gmp-6.1.0.tar.bz2: 確定
mpfr-3.1.4.tar.bz2: 確定
mpc-1.0.3.tar.gz: 確定
isl-0.16.1.tar.bz2: 確定
All prerequisites downloaded successfully.

如果出現如下資訊,則表示包:gmp-6.1.0.tar.bz2沒有下載成功:

2018-03-24 20:54:39 URL:http://gcc.parentingamerica.com/infrastructure/mpc-1.0.3.tar.gz [669925/669925] -> "./mpc-1.0.3.tar.gz" [1]
2018-03-24 20:56:16 URL:http://gcc.parentingamerica.com/infrastructure/isl-0.16.1.tar.bz2 [1626446/1626446] -> "./isl-0.16.1.tar.bz2" [1]
gmp-6.1.0.tar.bz2: 失敗
sha512sum: 警告:1 個校驗和不匹配
error: Cannot verify integrity of possibly corrupted file gmp-6.1.0.tar.bz2

這是因為網路連線不正常造成的,解決方案是,進入目錄“/home/davidhopper/code/gcc/gcc-7.3.0”,手動將已下載的“mpc-1.0.3.tar.gz”、“isl-0.16.1.tar.bz2”檔案刪除,重新執行bash contrib/download_prerequisites命令下載。如果仍然提示失敗,則應使用vi編輯器修改contrib/download_prerequisites
檔案裡的base_ur=...換為另一個能夠正常連線並下載的映象伺服器地址。

2.4 執行configure命令生成Makefile

再次進入構建資料夾,執行如下配置命令生成Makefile:

cd ~/code/gcc/gcc-7.3.0-build
../gcc-7.3.0/configure

結果又出了一個妖蛾子,錯誤提示如下:

configure: error: I suspect your system does not have 32-bit 
development libraries (libc and headers). If you have them, 
rerun configure with --enable-multilib. If you do not have them, 
and want to build a 64-bit-only compiler, rerun configure with 
--disable-multilib.

5
也就是說,configure推斷本機沒有32位開發庫,如果的確有就加上--enable-multilib選項,否則就使用--disable-multilib選項只構建64位版本。現在的機器誰還用32位系統,於是我立即重新執行配置程式如下:

../gcc-7.3.0/configure --disable-multilib

結果令人欣慰,總算在構建目錄“/home/davidhopper/code/gcc/gcc-7.3.0-build”中生成了Makefile。

2.5 執行make命令編譯構建GCC編譯器

接下來的事情似乎很簡單,只要執行make命令(需指出的是, Make程式支援併發處理,你的處理器有幾個核,就可以加上-j x選項,以便加快編譯速度)就可以編譯構建GCC編譯器了,事實證明,我又把問題估計簡單了一些。

cd ~/code/gcc/gcc-7.3.0-build
make -j 8

編譯了不一會,就出現如下錯誤:

checking LIBRARY_PATH variable... contains current directory
configure: error: 
*** LIBRARY_PATH shouldn't contain the current directory when
*** building gcc. Please change the environment variable
*** and run configure again.
出現這個錯誤的原因是由於環境變數的LD_LIBRARY_PATH中出現了當前目錄。
找了好久不知道是啥原因,因為不可能把這目錄放在環境變數啊。後來發現,
通常我們寫環境變數都喜歡寫:
export LD_LIBRARY_PATH = $LD_LIBRARY_PATH:foo/bar
如果一開始LD_LIBRARY_PATH不存在的話,這個上面這串環境變數開頭就是冒號,
這就把當前資料夾包含進去了。一般來說我們挺需要這種效果,因為在編譯的時候
可以include某些東西,但是對於編譯glibc來說這個是多餘的。
最簡單的解決方法就是unset LD_LIBRARY_PATH,這能把這個環境變數直接幹掉。

好吧,開始照方抓藥,重新執行如下命令:

unset LIBRARY_PATH
../gcc-7.3.0/configure --disable-multilib
make -j 8

在我機器上大約等了一個小時,最後全部構建成功。

2.6 執行sudo make install命令安裝GCC編譯器

cd ~/code/gcc/gcc-7.3.0-build
sudo make install

因為我在執行configure命令時,沒有指定安裝目錄,因此上述命令會將最新版本的GCC編譯器安裝到預設位置:/usr/local,也就是說,標頭檔案在/usr/local/include目錄,可執行檔案在/usr/local/bin目錄,庫檔案在/usr/local/lib目錄。

2.6 指定本機使用最新版本GCC編譯器

使用update-alternatives命令配置增加最新版本編譯器,注意:gcc是編譯C程式的預設程式,g++是編譯C++程式的預設程式。

# update-alternatives --install <連結> <名稱> <路徑> <優先順序>
sudo update-alternatives --install /usr/bin/gcc gcc /usr/local/bin/gcc 50
sudo update-alternatives --install /usr/bin/g++ g++ /usr/local/bin/g++ 50

使用下述命令查詢當前已經安裝的GCC編譯器版本:

# 查詢本機已有GCC編譯器情況
sudo update-alternatives --query gcc
# 查詢本機已有G++編譯器情況
sudo update-alternatives --query g++

我機器上的顯示結果為:

Name: gcc
Link: /usr/bin/gcc
Status: auto
Best: /usr/local/bin/gcc
Value: /usr/local/bin/gcc

Alternative: /usr/bin/gcc-5
Priority: 20

Alternative: /usr/local/bin/gcc
Priority: 50
Name: g++
Link: /usr/bin/g++
Status: auto
Best: /usr/local/bin/g++
Value: /usr/local/bin/g++

Alternative: /usr/bin/g++-5
Priority: 20

Alternative: /usr/local/bin/g++
Priority: 50

選擇預設使用的GCC編譯器版本:

# 互動配置GCC編譯器
sudo update-alternatives --config gcc
# 互動配置G++編譯器
sudo update-alternatives --config g++

在我機器上的結果如下,選擇預設選項“0”即可。

有 2 個候選項可用於替換 gcc (提供 /usr/bin/gcc)。

  選擇       路徑              優先順序  狀態
------------------------------------------------------------
* 0            /usr/local/bin/gcc   50        自動模式
  1            /usr/bin/gcc-5       20        手動模式
  2            /usr/local/bin/gcc   50        手動模式

要維持當前值[*]請按<回車鍵>,或者鍵入選擇的編號:
有 2 個候選項可用於替換 g++ (提供 /usr/bin/g++)。

  選擇       路徑              優先順序  狀態
------------------------------------------------------------
* 0            /usr/local/bin/g++   50        自動模式
  1            /usr/bin/g++-5       20        手動模式
  2            /usr/local/bin/g++   50        手動模式

要維持當前值[*]請按<回車鍵>,或者鍵入選擇的編號:

三、C++ 17標準程式測試

寫一個C++ 17標準中關於結構化繫結(structured bindings)的小程式,程式碼如下:

#include <iostream>
#include <tuple>
#include <map>
#include <stdexcept>

bool divide_remainder(int dividend, int divisor, int &fraction, int &remainder)
{
    if (divisor == 0)
    {
        return false;
    }
    fraction = dividend / divisor;
    remainder = dividend % divisor;
    return true;
}

std::pair<int, int> divide_remainder(int dividend, int divisor)
{
    if (divisor == 0)
    {
        throw std::runtime_error{"Attempt to divide by 0"};
    }
    return {dividend / divisor, dividend % divisor};
}

int main()
{
    { // old school way
        int fraction, remainder;
        const bool success{divide_remainder(16, 3, fraction, remainder)};
        if (success)
        {
            std::cout << "16 / 3 is " << fraction << " with a remainder of " << remainder << "\n";
        }
    }

    { // C++11 way
        const auto result(divide_remainder(16, 3));
        std::cout << "16 / 3 is " << result.first << " with a remainder of " << result.second << "\n";
    }

    { // C++11, ignoring fraction part of result
        int remainder;
        std::tie(std::ignore, remainder) = divide_remainder(16, 5);
        std::cout << "16 % 5 is " << remainder << "\n";
    }

    { // C++17, use structured bindings
        auto[fraction, remainder] = divide_remainder(16, 3);
        std::cout << "16 / 3 is " << fraction << " with a remainder of " << remainder << "\n";
    }

    { // C++17, decompose a tuple into individual vars
        std::tuple<int, float, long> tup{1, 2.0, 3};
        auto[a, b, c] = tup;
        std::cout << a << ", " << b << ", " << c << "\n";
    }

    { // C++17, use structured binding in for-loop

        std::map<std::string, size_t> animal_population{
            {"humans", 7000000000},
            {"chickens", 17863376000},
            {"camels", 24246291},
            {"sheep", 1086881528}
            /* … */
        };

        for (const auto & [ species, count ] : animal_population)
        {
            std::cout << "There are " << count << " " << species << " on this planet.\n";
        }
    }
}

編譯命令如下:

g++ -g -Wall -std=c++17 *.cpp -o test

執行測試程式及結果如下:

./test
16 / 3 is 5 with a remainder of 1
16 / 3 is 5 with a remainder of 1
16 % 5 is 1
16 / 3 is 5 with a remainder of 1
1, 2, 3
There are 24246291 camels on this planet.
There are 17863376000 chickens on this planet.
There are 7000000000 humans on this planet.
There are 1086881528 sheep on this planet.

四、可能遇到的問題

4.1 使用G++7.3.0構建多執行緒程式,執行程式時出現類似“./main: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.22’ not found (required by ./main)”的錯誤

寫一個使用C++ 17標準實現多執行緒的小程式,程式碼如下:

#include <iostream>
#include <queue>
#include <tuple>
#include <condition_variable>
#include <thread>

using namespace std;
using namespace chrono_literals;

queue<size_t> q;
mutex mut;
condition_variable cv;
bool finished = false;

void producer(size_t items) {
    for (size_t i = 0; i < items; ++i) {
        this_thread::sleep_for(100ms);
        {
            lock_guard<mutex> lk(mut);
            q.push(i);
        }
        cv.notify_all();
    }

    {
        lock_guard<mutex> lk(mut);
        finished = true;
    }
    cv.notify_all();
}

void comsumer() {
    while (!finished) {
        unique_lock<mutex> lk(mut);
        cv.wait(lk, []() {
            return !q.empty() || finished;
        });

        while (!q.empty()) {
            cout << "Got " << q.front() << " from queue. " << endl;
            q.pop();
        }
    }
}

int main() {
    thread t1(producer, 10);
    thread t2(comsumer);

    t1.join();
    t2.join();

    cout << "Finished! " << endl;
    return 0;
}

編譯命令如下:

g++ -g -Wall -std=c++17 -pthread *.cpp -o main

執行測試程式:

./main

提示如下錯誤資訊:

./main: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.22' not found (required by ./main)

6
這是因為沒有使用“libstdc++.so.6”版本不夠新造成的,解決方法如下:
首先,在GCC 7.3.0的安裝目錄(如果未更改,預設安裝路徑為:/usr/local)中查詢“libstdc++.so.6”,命令如下:

find /usr/local -name "libstdc++.so.6"

結果如下:

/usr/local/lib64/libstdc++.so.6

接著,將“/usr/lib/x86_64-linux-gnu/libstdc++.so.6”刪除,並建立一個新的符號連結檔案指向“/usr/local/lib64/libstdc++.so.6”,命令如下:

cd /usr/lib/x86_64-linux-gnu
sudo rm -f libstdc++.so.6
sudo ln -s /usr/local/lib64/libstdc++.so.6

然後,確認檢查新的“libstdc++.so.6”檔案已包含`GLIBCXX_3.4.22’版本(該步驟可不執行)。

strings ./libstdc++.so.6 | grep GLIBC

7
最後,進入測試程式所在目錄,重新執行生成的程式:

cd ~/code/C++17/SimpleProducerConsumerThread/ 
./main

結果如下:

Got 0 from queue. 
Got 1 from queue. 
Got 2 from queue. 
Got 3 from queue. 
Got 4 from queue. 
Got 5 from queue. 
Got 6 from queue. 
Got 7 from queue. 
Got 8 from queue. 
Got 9 from queue. 
Finished! 

4.2 無法將GCC編譯器版本降級

最新版本的GCC編譯器雖然用起來很舒服,但一些舊程式碼可能還是需要老版本的GCC編譯器才能編譯,這時我們自然想到使用sudo update-alternatives --config gcc命令去配置版本,但經過實踐發現,無論我怎麼設定選項,gcc -v命令總是輸出如下資訊:

使用內建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-pc-linux-gnu/7.3.0/lto-wrapper
目標:x86_64-pc-linux-gnu
配置為:../gcc-7.3.0/configure --disable-multilib
執行緒模型:posix
gcc 版本 7.3.0 (GCC)

也就是說,無法將GCC版本降級。
該問題產生的原因是,我們將GCC7.3.0的優先順序設定得太高了。由於GCC7.3.0的優先順序高、版本也新,無論我們怎麼手動選擇GCC版本,系統仍然會匹配版本較新的GCC程式。
解決方法是:將老版本的GCC程式優先順序設定得更高。具體操作如下:

# 1.刪除原有低優先順序的老GCC配置項
sudo update-alternatives --remove gcc /usr/bin/gcc-5
sudo update-alternatives --remove g++ /usr/bin/g++-5
# 2.以更高的優先順序重新安裝老GCC配置項
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 70
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 70
# 3.這時就可以更改當前使用的GCC版本了
sudo update-alternatives --config gcc
sudo update-alternatives --config g++