严正声明:本文系作者davidhopper原创,未经许可,不得转载。
2019年8月2日更新:
本文方法适用于GCC 9.1.0,只需将原文中的GCC 7.3.0替换为GCC 9.1.0即可。
为什么要更新到GCC 9.1.0?因为该版本可支持C++ 17标准的并行策略。
如何使用C++17标准的并行策略,参考我的另一篇博客:《Ubuntu 16.04系统中使用GCC 9.1及Intel TBB库运行C++17 STL并行算法库》。
2017年底,C++17标准正式颁布,该标准的最大贡献是,提供了STL库算法的并行运算版本,对于我这种喜欢追求算法性能的程序员而言,无疑是一个极大的福音。幸运地是,Linux系统标准编译器GCC能完美地支持C++ 17标准,但需升级到7.0以上版本;不幸地是,Ubuntu 16.04版本自带的GCC版本为5.4.0,可支持C++ 14标准,但基本不支持C++ 17标准。怎么办?那就从零开始,从GCC官方网站下载、安装最新标准的编译器吧。
进入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版源代码压缩包“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”中。
由于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”,整个目录结构如下图所示:
此时,我以为只要进入构建文件夹,然后执行如下配置命令就可以生成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=...
换为另一个能够正常连接并下载的镜像服务器地址。
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。
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
在我机器上大约等了一个小时,最后全部构建成功。
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
目录。
使用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 手动模式
要维持当前值[*]请按<回车键>,或者键入选择的编号:
可以使用如下命令查询当前的gcc
及g++
版本:
# 查询gcc版本
gcc --version
# 查询g++版本
g++ --version
写一个C++ 17标准中关于结构化绑定(structured bindings)的小程序,代码如下:
#include
#include
#include
#include
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.
写一个使用C++ 17标准实现多线程的小程序,代码如下:
#include
#include
#include
#include
#include
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
复制到本地,命令如下:
cd /usr/lib/x86_64-linux-gnu
# 备份原有版本
sudo mv libstdc++.so.6 libstdc++.so.6.bk
# 复制新版本
sudo cp /usr/local/lib64/libstdc++.so.6 ./
# 更新共享库缓存
sudo ldconfig
然后,确认检查新的“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!
最新版本的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++