🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 更新gcc 红帽其实已经编译好了高版本的gcc,但未更新到base和epel这两个常用的源中,而是放在`scl`中。第一步便是安装`scl`: ~~~ yum install -y centos-release-scl glibc-static ~~~ 如果你之前用过`grouplist/install`等命令,应该知道gcc包含在`Development Tools`这个组中。那么`scl`中的gcc/g++软件包的前缀都是`devtoolset`,也就不难理解了。安装gcc 6版本的命令是: ~~~ yum install -y devtoolset-8-gcc devtoolset-8-gcc-c++ ~~~ `devtoolset-6`中的gcc版本为gcc 6,除此之外还有如下版本: * devtoolset-3: gcc 4.9 * devtoolset-4: gcc 5 * devtoolset-6: gcc 6 * devtoolset-7: gcc 7 * devtoolset-8: gcc 8 至于为什么没有devtoolset-5,我也不清楚,估计是包含在devtoolset-4中了吧。值得说明的是这些软件包可以同时安装,不会相互覆盖和冲突,也不会覆盖系统的版本。即可以在系统中同时安装gcc 6, gcc 7, gcc 8等多个版本。 因为不会覆盖系统默认的gcc,使用这些软件的方法有四种: 1. 使用绝对路径; 2. 添加可执行文件路径到PATH环境变量; 3. 使用官方推荐的加载命令:`scl enable devtoolset-x bash`, x为要启用的版本; 4. 执行安装软件自带的脚本: `source /opt/rh/devtoolset-x/enable`,x为要启用的版本。 推荐使用最后两种方式,例如启用gcc 6: `source /opt/rh/devtoolset-6/enable`,然后输入`gcc -v`查看版本已经变成gcc 6.3.1。使用类似的命令可以随时在多个gcc版本中切换。如果希望长期使用高版本,可将此命令写入`.bashrc`等配置文件。 ~~~ echo "source /opt/rh/devtoolset-8/enable" >>/etc/profile ~~~ 最后说一下,scl以及scl-rh源中的软件包都安装在/opt/rh/目录下,包含可执行文件、配置等。所以启用命令的路径是`/opt/rh/xxx/enable`,安装的服务重启命令则可能是`systemctl restart rh-xxx`,需要加rh或scl前缀以区别其他源的包。如果你用过remi/gitlab等源,其行为方式也是类似的。 # 工作流 gcc编译器从拿到一个c源文件到生成一个可执行程序,中间一共经历了四个步骤: ![](https://img.kancloud.cn/4b/55/4b55bf2a87b293f319405e06623affff_805x126.png) 四个步骤并不是gcc独立完成的,而是在内部调用了其他工具,从而完成了整个工作流程: ![](https://img.kancloud.cn/43/d4/43d4edb301b4f8400db1487e4b45a02b_772x208.png) gcc工作的流程 > deng@itcast:~/share/3rd/1gcc$ ls 1hello.c > > 第一步: 进行预处理 > > deng@itcast:~/share/3rd/1gcc$ gcc -E 1hello.c -o 1hello.i > > 第二步: 生成汇编文件 > > deng@itcast:~/share/3rd/1gcc$ gcc -S 1hello.i -o 1hello.s > > 第三步: 生成目标代码 > > deng@itcast:~/share/3rd/1gcc$ gcc -c 1hello.s -o 1hello.o > > 第四步: 生成可以执行文件 > > deng@itcast:~/share/3rd/1gcc$ gcc 1hello.o -o 1hello 第五步: 执行 deng@itcast:~/share/3rd/1gcc​$ ./1hello hello itcast 直接将源文件生成一个可以执行文件 > deng@itcast:~/share/3rd/1gcc$ gcc 1hello.c -o 1hello deng@itcast:~/share/3rd/1gcc​$ ./1hello hello itcast 如果不指定输出文件名字, gcc编译器会生成一个默认的可以执行a.out > deng@itcast:~/share/3rd/1gcc$ gcc 1hello.c > deng@itcast:~/share/3rd/1gcc​$ ls 1hello 1hello.c 1hello.i 1hello.o 1hello.s a.out deng@itcast:~/share/3rd/1gcc​$ ./a.out > hello itcast # 常用选项 | **选项** | **作用** | | --- | --- | | \-o file | 指定生成的输出文件名为file | | \-E | 只进行预处理 | | \-S(大写) | 只进行预处理和编译 | | \-c(小写) | 只进行预处理、编译和汇编 | | \-v / --version | 查看gcc版本号 | | \-g | 包含调试信息 | | \-On n=0~3 | 编译优化,n越大优化得越多 | | \-Wall | 提示更多警告信息 | | \-D | 编译时定义宏 | 显示所有的警告信息 > gcc -Wall test.c 将警告信息当做错误处理 > gcc -Wall -Werror test.c 测试程序(-D选项): ~~~ #include <stdio.h> ​ int main(void) { printf("SIZE: %d\n", SIZE); return 0; } ~~~ ~~~ deng@itcast:~/test$ gcc 2test.c -DSIZE=10 deng@itcast:~/test$ ./a.out SIZE: 10 ~~~ # 静态链接和动态链接 链接分为两种:**静态链接**、**动态链接**。 **静态、动态编译对比** 前面我们编写的应用程序大量用到了标准库函数,系统默认采用动态链接的方式进行编译程序,若想采用静态编译,加入-static参数。 以下是分别采用动态编译、静态编译时文件对比: 测试程序(test.c)如下: ~~~ #include <stdio.h> int main(void) { printf("hello world \n"); return 0; } ~~~ 编译: > deng@itcast:~/test$ gcc test.c -o test\_share > > deng@itcast:~/test$ gcc -static test.c -o test\_static # 静态库和动态库 所谓“程序库”,简单说,就是包含了数据和执行码的文件。其不能单独执行,可以作为其它执行程序的一部分来完成某些功能。 库的存在可以使得程序模块化,可以加快程序的再编译,可以实现代码重用,可以使得程序便于升级。 程序库可分**静态库(static library)**和**共享库(shared library)** ## 静态库制作 静态库可以认为是一些目标代码的集合,是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。 按照习惯,一般以“.a”做为文件后缀名。静态库的命名一般分为三个部分: * 前缀:lib * 库名称:自己定义即可 * 后缀:.a 所以最终的静态库的名字应该为:**libxxx.a** ![](https://img.kancloud.cn/ee/81/ee816bcc97df817ccafbf5a4adff0d5f_546x341.png) 步骤1:将c源文件生成对应的.o文件 > deng@itcast:~/test/3static\_lib$ gcc -c add.c -o add.o > deng@itcast:~/test/3static\_lib​$ gcc -c sub.c -o sub.o > deng@itcast:~/test/3static\_lib​$ gcc -c mul.c -o mul.o > deng@itcast:~/test/3static\_lib​$ gcc -c div.c -o div.o 步骤2:使用打包工具ar将准备好的.o文件打包为.a文件 libtest.a > deng@itcast: ar -rcs libtest.a add.o sub.o mul.o div.o **在使用ar工具是时候需要添加参数:rcs** * r更新 * c创建 * s建立索引 **静态库使用** 静态库制作完成之后,需要将.a文件和头文件一起发布给用户。 假设测试文件为main.c,静态库文件为libtest.a头文件为head.h 编译命令: > deng@itcast: gcc test.c -L./ -I./ -ltest -o test 参数说明: * \-L:表示要连接的库所在目录 * \-I./: I(大写i) 表示指定头文件的目录为当前目录 * \-l(小写L):**指定链接时需要的库,去掉前缀和后缀** ![](https://img.kancloud.cn/cc/ef/cceff1734d1092b9c5b3278721acbded_401x318.png) add.h ~~~ #ifndef __ADD_H__ #define __ADD_H__ int add(int x, int y); #endif /* __ADD_H__ */ ~~~ add.c ~~~ #include "add.h" int add(int x, int y) { return x + y; } ~~~ test.c ~~~ #include <stdio.h> #include "add.h" int main(void) { printf("1 + 2 = %d\n", add(1, 2)); return 0; } ~~~ ## 动态库制作 共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。 动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。 按照习惯,一般以“.so”做为文件后缀名。共享库的命名一般分为三个部分: * 前缀:lib * 库名称:自己定义即可 * 后缀:.so 所以最终的动态库的名字应该为:`libxxx.so` ![](https://img.kancloud.cn/53/f3/53f3cc74abc78768f1d109cd7209e390_402x326.png) **1)动态库制作** 步骤一:生成目标文件,此时要加编译选项:-fPIC(fpic) > deng@itcast:~/test/5share\_lib$ gcc -fPIC -c add.c > deng@itcast:~/test/5share\_lib​$ gcc -fPIC -c sub.c > deng@itcast:~/test/5share\_lib$ gcc -fPIC -c mul.c > deng@itcast:~/test/5share\_lib​$ gcc -fPIC -c div.c 参数:-fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。 步骤二:生成共享库,此时要加链接器选项: -shared(指定生成动态链接库) > deng@itcast:~/test/5share\_lib$ gcc -shared add.o sub.o mul.o div.o -o libtest.so 步骤三: 通过nm命令查看对应的函数 > deng@itcast: nm libtest.so | grep add > deng@itcast: nm libtest.so | grep sub ldd查看可执行文件的依赖的动态库 > deng@itcast:~/share/3rd/2share\_test$ ldd test > linux-vdso.so.1 => (0x00007ffcf89d4000) libtest.so => /lib/libtest.so (0x00007f81b5612000) libc.so.6 => /lib/x86\_64-linux-gnu/libc.so.6 (0x00007f81b5248000) /lib64/ld-linux-x86-64.so.2 (0x00005562d0cff000) **2)动态库测试** 引用动态库编译成可执行文件(跟静态库方式一样) > deng@itcast:~/test/6share\_test$ gcc test.c -L. -I. -ltest (-I. 大写i -ltest 小写L) 然后运行:./a.out,发现竟然报错了!!! ![](https://img.kancloud.cn/d4/d8/d4d83aed86419bca70cb55413b55dbdf_792x154.png) * 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。 * 对于elf格式的可执行程序,是由`ld-linux.so`来完成的,它先后搜索elf文件的 DT\_RPATH段 — 环境变量LD\_LIBRARY\_PATH — /etc/ld.so.cache文件列表 —**/lib/, /usr/lib**目录找到库文件后将其载入内存。 **3)如何让系统找到动态库** * 拷贝自己制作的共享库到/lib或者/usr/lib(不能是/lib64目录) * 临时设置LD\_LIBRARY\_PATH: > export LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:库路径 * 永久设置,把export LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:库路径,设置到~/.bashrc或者 /etc/profile文件中 > deng@itcast:~/share/3rd/2share\_test$ vim ~/.bashrc > > 最后一行添加如下内容: > > export LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:/home/deng/share/3rd/2share\_test 使环境变量生效 > deng@itcast: source ~/.bashrc > deng@itcast: ./test > a + b = 20 a - b = 10 * 将其添加到 /etc/ld.so.conf文件中 编辑/etc/ld.so.conf文件,加入库文件所在目录的路径 运行sudo ldconfig -v,该命令会重建/etc/ld.so.cache文件 > deng@itcast: sudo vim /etc/ld.so.conf > > 文件最后添加动态库路径(绝对路径) ![](https://img.kancloud.cn/90/a8/90a8f53e52a97ac48099a627062e32bb_496x143.png) * > 使生效 > deng@itcast: sudo ldconfig -v 或者使用符号链接, 但是一定要使用绝对路径 > deng@itcast: sudo ln -s /home/deng/test/6share\_test/libtest.so /lib/libtest.so