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系统的二进制版本,这不是赤裸裸地歧视吗?话虽如此,但活人还能让尿憋死?既然官网不提供,那我就自己下载源代码编译、安装。
目前,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/。
二、编译安装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”,整个目录结构如下图所示:
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.
注意:上面的提示信息存在一个坑,就是“ftp://gcc.gnu.org/pub/gcc/infrastructure/”现在根本不存在,你如果进入该网页去下载依赖包,恐怕是到死也完不成任务,因此必须将其替换为镜像服务器地址。例如,我选择的老毛子服务器地址为:ftp://gcc.gnu.org/pub/gcc/infrastructure/。
下载方法有两种:
第一种是笨办法,老老实实地进入镜像服务器网址,然后依次下载: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.
也就是说,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.
于是立即在网上搜索解决方案,找到如下类似的靠谱答案(http://friskit.me/2016/02/13/ld_library_path-shouldt-contain-the-current-dir/):
出现这个错误的原因是由于环境变量的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)
这是因为没有使用“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”复制到“/usr/lib/x86_64-linux-gnu/”目录,命令如下:
cd /usr/lib/x86_64-linux-gnu
sudo rm -rf libstdc++.so.6
sudo cp /usr/local/lib64/libstdc++.so.6 ./
然后,确认检查新的“libstdc++.so.6”文件已包含`GLIBCXX_3.4.22’版本(该步骤可不执行)。
strings ./libstdc++.so.6 | grep GLIBC
最后,进入测试程序所在目录,重新运行生成的程序:
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++