Linux升级与使用GCC


GNU项目中的一个子项目GCC(GNU Compiler Collection)是一个编译器套装,是GNU计划的关键部分,也是GNU最优秀的软件之一。其最初用于编译C语言,随着项目的发展GCC已经成为了能够编译C、C++、Java、Ada、fortran、Object C、Go语言的编译器大家族。
GCC由cpp(预处理器)、gcc(C编译器)、g++(C++编译器)、binutils(Binary Utilities二进制工具)等工具组成。binutils是辅助gcc的主要软件,常用的工具有:as(汇编器)、ld(链接器)、ar(ar工具)等等。
在各大发行版本Linux中,其自带的gcc安装源版本都比较老旧,我所使用CentOS 6系统中,gcc版本只有4.4.7,而debian 7的是4.7。最近要安装的一些软件要求gcc 4.9+,只能手动升级gcc。
下载最新版本gcc
下载的是5.2,可以从最近的下载镜像,由于gcc本身比较大,所以下载会需要一些时间。如果下载很慢,也可以到gcc官网查找较快的下载镜像。gcc安装文件下载完成后,执行以下命令解压文件:
tar -xjf gcc-5.2.0.tar.bz2
下载gcc依赖文件和库
解压完成后,执行以下命令进入工作目录:
cd gcc-5.2.0
执行download_prerequisites脚本,下载gcc依赖文件和库:
./contrib/download_prerequisites
download_prerequisites脚本,会下载安装gcc所需的mpfr、gmp和mpc文件,下面是目前下载的版本。
./contrib/download_prerequisites
ftp://gcc.gnu.org/pub/gcc/infrastructure/mpfr-2.4.2.tar.bz2
ftp://gcc.gnu.org/pub/gcc/infrastructure/gmp-4.3.2.tar.bz2
ftp://gcc.gnu.org/pub/gcc/infrastructure/mpc-0.8.1.tar.gz
ftp://gcc.gnu.org/pub/gcc/infrastructure/isl-0.14.tar.bz2
配置安装gcc
可以建立一个输出目录,编译时所有生成的中间文件都放到该目录下:
mkdir gcc-temp
工作目录切换至输出目录,并在其中执行配置和安装:
cd gcc-temp
当然也可以在其解压目录中直接编译,执行configure配置安装文件:
./configure --enable-checking=release --enable-languages=c,c++ --disable-multilib --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
配置完成后,执行以下命令,编译gcc:
make -j 8
注意:编译gcc时间较长,在dell i5台式机上用时1小时。如果要更快一点,机器又支持超线程的话:make后面跟 -jN,-j 后面的数值是线程数,一般是 CPU 核心数的 2 倍。假如是4核心 CPU,则使用 -j8。configure 参数中,不指定 build、host、target参数,将显示unknown。
编译完成后,安装gcc:
make install
安装完成后还需要替换系统默认的gcc,执行以下命令,查找5.2版本的安装文件:
ls /usr/local/bin | grep gcc
输出如下:
gcc
gcc-ar
gcc-nm
gcc-ranlib
x86_64-unknown-linux-gnu-gcc
x86_64-unknown-linux-gnu-gcc-5.2.0 //就是这个
x86_64-unknown-linux-gnu-gcc-ar
x86_64-unknown-linux-gnu-gcc-nm
x86_64-unknown-linux-gnu-gcc-ranlib
debian执行升级命令:
/usr/sbin/update-alternatives --install /usr/bin/gcc gcc /usr/local/bin/x86_64-unknown-linux-gnu-gcc-5.2.0 52
updatedb && sudo ldconfig
locate gcc
下面将验证安装
查看gcc版本:
gcc -v
查看g++版本:
g++ -v
或使用which gcc查看gcc安装目录,在安装目录下执行-v命令。例如,安装目录为/usr/local/bin/gcc:
/usr/local/bin/gcc -v
如果输出中有类似以下行,说明安装成功:
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-linux-gnu/5.2.0/lto-wrapper
目标:x86_64-linux-gnu
配置为:./configure --enable-checking=release --enable-languages=c,c++ --disable-multilib --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
线程模型:posix
gcc 版本 5.2.0 (GCC)
可能出现的问题:
1、/usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found
编译程序或运行其它程序时,时不时会出现类似/usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found的问题。这是因为升级gcc时,生成的动态库没有替换老版本gcc的动态库导致的,将gcc最新版本的动态库替换系统中老版本的动态库即可解决。
检查动态库:
strings /usr/lib64/libstdc++.so.6 | grep GLIBC
输出结果如下:
GLIBCXX_3.4
GLIBCXX_3.4.1
GLIBCXX_3.4.2
GLIBCXX_3.4.3
GLIBCXX_3.4.4
GLIBCXX_3.4.5
GLIBCXX_3.4.6
GLIBCXX_3.4.7
GLIBCXX_3.4.8
GLIBCXX_3.4.9
GLIBCXX_3.4.10
GLIBCXX_3.4.11
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBCXX_FORCE_NEW
GLIBCXX_DEBUG_MESSAGE_LENGTH
从以上输出可以看出,gcc的动态库还是旧版本的。说明出现这些问题,是因为升级gcc时,生成的动态库没有替换老版本gcc的动态库。 执行以下命令,查找编译gcc时生成的最新动态库:
find / -name "libstdc++.so*"
输出如下:
/home/freeoa/gcc-temp/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so
/home/freeoa/gcc-temp/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
/home/freeoa/gcc-temp/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.21 //最新动态库
……
/home/freeoa/gcc-temp是升级gcc时的输出目录。将上面的最新动态库libstdc++.so.6.0.21复制到/usr/lib64目录下:
cp /home/freeoa/gcc-temp/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.21 /usr/lib64
复制后,修改系统默认动态库的指向,即:重建默认库的软连接。
切换工作目录至/usr/lib64:
cd /usr/lib64
删除原来软连接:
rm -rf libstdc++.so.6
将默认库的软连接指向最新动态库:
ln -s libstdc++.so.6.0.21 libstdc++.so.6
默认动态库升级完成。重新运行以下命令检查动态库:
strings /usr/lib64/libstdc++.so.6 | grep GLIBC
CentOS 6.x通过yum安装高版本Gcc
CentOS 6 的gcc的版本是 4.4,很多时候在编译安装软件都需要高版本的 GCC,否则就会报错。需要注意的是,在编译安装 GCC 之前,系统里必须先要通过 yum 安装老版本的 GCC 和依赖库。如果是在 x86_64 系统下编译的话,还需要安装 libgcc.i686 glibc-devel.i686 才行。编译安装 GCC 内存不小于 1GB,Swap 不小于 1GB,硬盘最低不小于 10GB,否则极有可能会中途报错退出。
gcc 4.8 安装
curl -Lks http://www.hop5.in/yum/el6/hop5.repo > /etc/yum.repos.d/hop5.repo
yum install gcc gcc-g++
gcc --version
gcc 4.9 安装
yum install centos-release-scl
yum install devtoolset-3-toolchain
scl enable devtoolset-3 bash
gcc --version
gcc 5.2 安装
yum install centos-release-scl
yum install devtoolset-4-toolchain
scl enable devtoolset-4 bash
gcc --version
scl enable devtoolset-3 bash只是临时覆盖系统原有的GCC引用,如果想永久覆盖,可在root/.bashrc文件中添加source /opt/rh/devtoolset-3/enable
Gcc仅仅作为真实的编译器和链接器的入口。它会在需要的时候调用其它组件(预处理器、汇编器、链接器),并且会传一些额外的参数给编译器和连接器。输入文件的类型和传给gcc的参数决定了gcc调用哪些组件。其所能识别的文件扩展名如下:
.c C语言文件
.i 预处理后的C语言文件
.C、.cc、.cp、.cpp、.c++、.cxx C++语言文件
.ii 预处理后的C++语言文件
.S 汇编文件
.s 预处理后的汇编文件
.o 编译后的目标文件
.a 目标文件的静态链接库(链接时使用)
.so 目标文件的动态链接库(链接、运行时使用)
编译过程示意图:

GCC编译选项
对Linux开发人员来讲,GCC给出的警告信息是很有价值的,它们不仅可以帮助程序员写出更加健壮的程序,而且还是跟踪和调试程序的有力工具。建议在用GCC编译源代码时始终带上-Wall选项,养成良好的习惯。
-Wall显示所有的警告信息。
-Wall选项可以打开所有类型的语法警告,以便于确定程序源代码是否是正确的,并且尽可能实现可移植性。
-pedantic以 ANSI/ISO C 标准列出的所有警告。
当GCC在编译不符合 ANSI/ISO C 语言标准的源代码时,如果在编译指令中加上了-pedantic选项,那么源程序中使用了扩展语法的地方将产生相应的警告信息。如果代码中的成员变量的初始化顺序和它们实际执行时初始化顺序不一致,给出警告。
----------------
常用选项
--help
--target-help
显示 gcc 帮助说明。‘target-help’是显示目标机器特定的命令行选项。
--version
显示 gcc 版本号和版权信息。
-o outfile
输出到指定的文件。
-x language
指明使用的编程语言。允许的语言包括:c c++ assembler none。‘none’意味着恢复默认行为,即根据文件的扩展名猜测源文件的语言。
-v
打印较多信息,显示编译器调用的程序。
-###
与 -v 类似,但选项被引号括住,并且不执行命令。
-E
仅作预处理,不进行编译、汇编和链接。
-S
仅编译到汇编语言,不进行汇编和链接。
-c
编译、汇编到目标代码,不进行链接。
-pipe
使用管道代替临时文件。
-combine
将多个源文件一次性传递给汇编器。
其他GCC选项更多有用的GCC选项:
-l library
-llibrary
进行链接时搜索名为library的库。
例子:$ gcc test.c -lm -o test
-Idir
把dir加入到搜索头文件的路径列表中。
例子:$ gcc test.c -I../inc -o test
-Ldir
把dir加入到搜索库文件的路径列表中。
例子:$ gcc -I/home/foo -L/home/foo -ltest test.c -o test
-Dname
预定义一个名为name 的宏,值为1。
例子:$ gcc -DTEST_CONFIG test.c -o test
-Dname=definition
预定义名为name,值为definition 的宏。
-ggdb
-ggdblevel
为调试器 gdb 生成调试信息。level 可以为1,2,3,默认值为2。
-g
-glevel
生成操作系统本地格式的调试信息。-g 和 -ggdb 并不太相同,-g 会生成 gdb 之外的信息。level 取值同上。
-s
去除可执行文件中的符号表和重定位信息。用于减小可执行文件的大小。
-M
告诉预处理器输出一个适合make的规则,用于描述各目标文件的依赖关系。对于每个 源文件,预处理器输出 一个make规则,该规则的目标项(target)是源文件对应的目标文件名,依赖项(dependency)是源文件中 `#include引用的所有文件。生成的规则可 以是单行,但如果太长,就用`\'-换行符续成多行。规则 显示在标准输出,不产生预处理过的C程序。
-C
告诉预处理器不要丢弃注释。配合`-E'选项使用。
-P
告诉预处理器不要产生`#line'命令。配合`-E'选项使用。
-static
在支持动态链接的系统上,阻止连接共享库。该选项在其它系统上无效。
-nostdlib
不连接系统标准启动文件和标准库文件,只把指定的文件传递给连接器。
优化项|优化等级
-O0禁止编译器进行优化。默认为此项。
-O1尝试优化编译时间和可执行文件大小。
-O2更多的优化,会尝试几乎全部的优化功能,但不会进行“空间换时间”的优化方法。
-O3在O2的基础上再打开一些优化选项:-finline-functions,-funswitch-loops 和 -fgcse-after-reload。
-Os对生成文件大小进行优化。它会打开 -O2 开的除了会那些增加文件大小的全部选项。
gcc默认提供了5级优:
-O0:无优化(默认)。
-O和-O1:使用能减少目标文件大小以及执行时间并且不会使编译时间明显增加的优化,在编译大型程序的时候会显著增加编译时内存的使用。
-O2:包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化,编译器不执行循环展开以及函数内联。此选项将增加编译时间和目标文件的执行性能。
-Os:专门优化目标文件大小,执行所有的不增加目标文件大小的-O2优化选项。并且执行专门减小目标文件大小的优化选项。
-O3:打开所有-O2的优化选项并且增加 -finline-functions,-funswitch-loops,-fpredictive-commoning,-fgcse-after-reload and -ftree-vectorize优化选项。
--------
Debug选项:
在gcc编译源代码时指定-g选项可以产生带有调试信息的目标代码,它可以为多个不同平台上帝不同调试器提供调试信息,默认gcc产生的调试信息是为gdb使用的,可以使用-gformat指定要生成的调试信息的格式以提供给其他平台的其他调试器使用。常用的格式有:
-ggdb:生成gdb专用的调试信息,使用最适合的格式(DWARF2,stabs等)会有一些gdb专用的扩展,可能造成其他调试器无法运行.
-gstabs:使用 stabs格式,不包含gdb扩展,stabs常用于BSD系统的DBX调试器.
-gcoff:产生COFF格式的调试信息,常用于System V下的SDB调试器;
-gxcoff:产生XCOFF格式的调试信息,用于IBM的RS/6000下的DBX调试器;
-gdwarf-2:产生DWARF version2 的格式的调试信息,常用于IRIXX6上的DBX调试器.GCC会使用DWARF version3的一些特性.
可以指定调试信息的等级,在指定的调试格式后面加上等级,如:-ggdb 2等,0代表不产生调试信息.在使用-gdwarf-2时因为最早的格式为-gdwarf2会造成混乱,所以要额外使用一个-glevel来指定调试信息的等级,其他格式选项也可以另外指定等级.
gcc可以使用-p选项指定生成信息以供porf使用。
--------
其他选项
-finline-functions
把所有简单的函数内联进调用者。编译器会探索式地决定哪些函数足够简单,值得做这种内联。
-fstrict-aliasing
施加最强的别名规则(aliasing rules)。
标准Standard
-ansi
支持符合ANSI标准的C程序。这样就会关闭GNU C中某些不兼容ANSI C的特性。
-std=c89 指明使用标准 ISO C90 作为标准来编译程序。
-std=c99 指明使用标准 ISO C99 作为标准来编译程序。
-std=c++98 指明使用标准 C++98 作为标准来编译程序。
-std=gnu9x 使用 ISO C99 再加上 GNU 的一些扩展。
-fno-asm
不把asm, inline或typeof当作关键字,因此这些词可以用做标识符。用 __asm__、__inline__和__typeof__能够替代它们。 `-ansi' 隐含声明了`-fno-asm'。
-fgnu89-inline
告诉编译器在 C99 模式下看到 inline 函数时使用传统的 GNU 句法。
C options
-fsigned-char
把char定义为有符号类型,如同signed char
-funsigned-char
把char定义为无符号类型,如同unsigned char。
-traditional
尝试支持传统C编译器的某些方面。详见GNU C手册。
-fno-builtin
-fno-builtin-function
不接受没有 __builtin_ 前缀的函数作为内建函数。
-trigraphs
支持ANSI C的三联符( trigraphs)。`-ansi'选项隐含声明了此选项。
-fsigned-bitfields
-funsigned-bitfields
如果没有明确声明`signed'或`unsigned'修饰符,这些选项用来定义有符号位域或无符号位域。缺省情况下,位域是有符号的,因为它们继承的基本整数类型,如int,是有符号数。
-fno-strict-aliasing
-fstrict-aliasing表示启用严格别名规则,-fno-strict-aliasing表示禁用严格别名规则,当gcc的编译优化参数为-O2、-O3和-Os时,默认会打开-fstrict-aliasing。
--------
fPIC选项
-fPIC的作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
gcc -shared -fPIC -o 1.so 1.c
fPIC参数使.so文件的代码段变为真正意义上的共享;如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy。每个copy都不一样,取决于这个.so文件代码段和数据段内存映射的位置。
也就是不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的(因为它里面的代码并不是位置无关代码)。如果被多个应用程序共同使用,那么它们必须每个程序维护一份.so的代码副本了(因为.so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。我们总是用fPIC来生成so,也从来不用fPIC来生成.a;fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目。
因此,不用fPIC编译so并不总是不好。如果满足以下4个需求/条件:
1.该库可能需要经常更新
2.该库需要非常高的效率(尤其是有很多全局量的使用时)
3.该库并不很大
4.该库基本不需要被多个应用程序共享
如果用没有加这个参数的编译后的共享库,也可以使用的话,可能是两个原因:
1.gcc默认开启-fPIC选项
2.loader使你的代码位置无关
从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。参见其手册页中有如下的描述:
-shared
Produce a shared object which can then be linked with other objects to form an executable. Not all systems support this option. For predictable results, you must also specify the same set of options that were used to generate code (`-fpic', `-fPIC', or model suboptions) when you specify this option.(1)
-fPIC 使用后会生成 PIC 代码,.so 要求为 PIC,以达到动态链接的目的,否则无法实现动态链接。
non-PIC 与 PIC 代码的区别主要在于 access global data, jump label 的不同。
比如一条 access global data 的指令,
non-PIC 的形势是:ld r3, var1
PIC 的形式则是:ld r3, var1-offset@GOT,意思是从 GOT 表的 index 为var1-offset 的地方处指示的地址处装载一个值,即var1-offset@GOT处的4个 byte 其实就是 var1 的地址。这个地址只有在运行的时候才知道,是由 dynamic-loader(ld-linux.so) 填进去的。
再比如 jump label 指令
non-PIC 的形势是:jump printf,意思是调用 printf。
PIC 的形式则是:jump printf-offset@GOT,
意思是跳到 GOT 表的 index 为 printf-offset 的地方处指示的地址去执行,这个地址处的代码摆放在 .plt section,每个外部函数对应一段这样的代码,其功能是呼叫dynamic-loader(ld-linux.so) 来查找函数的地址(本例中是 printf),然后将其地址写到 GOT 表的 index 为 printf-offset 的地方,同时执行这个函数。这样,第2次呼叫 printf 的时候,就会直接跳到 printf 的地址,而不必再查找了。
GOT 是 data section,是一个 table,除专用的几个 entry,每个 entry 的内容可以再执行的时候修改;
PLT 是 text section,是一段一段的 code,执行中不需要修改。
每个 target 实现 PIC 的机制不同,但大同小异。比如 MIPS 没有 .plt,而是叫 .stub,功能和 .plt 一样。
可见,动态链接执行很复杂,比静态链接执行时间长;但极大的节省了存储空间与大小,PIC 和动态链接技术是计算机发展史上非常重要的一个里程碑。
gcc manul上面有说
-fpic If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC and 32k on the m68k and RS/6000. The 386 has no such limit.)
-fPIC If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.
关键在于GOT全局偏移量表里面的跳转项大小。
intel处理器应该是统一4字节,没有问题。
powerpc上由于汇编码或者机器码的特殊要求,所以跳转项分为短、长两种。
-fpic为了节约内存,在GOT里面预留了“短”长度;而-fPIC则采用了更大的跳转项。
GCC警告选项
警告不是错误的,但是有风险或表明可能有错误。
英文原文:Warning Options - Using the GNU Compiler Collection (GCC)
加上-Wall吧,gcc 默认不加参数的情况下连定义了返回值的函数没有返回值都不报错。
----------------
开启和关闭告警方法
1、-w (小写)禁止所有警告消息
2、以“-W”(大写)开头开启特定的警告
例如:
-Wreturn-type(返回值告警),
-Wsign-compare(有符号和无符号对比告警)
-Wall (除extra外的所有告警)
-Wextra (all外的其他告警)
如$ gcc -Wall test.c -o test
3、以“-Wno-”开头关闭特定的警告
例如:
-Wno-return-type (取消返回值告警)
-Wno-sign-compare(取消有符号和无符号对比告警)
如:$ gcc -Wall -Wno-unused test.c -o test
批量开启告警(即-Wall和-Wextra 批量开启的告警)
某些选项(如-Wall和-Wextra )会打开其他选项,例如-Wunused;这可能会启用其他选项,例如-Wunused-value。
--------
-Wall
该选项相当于同时使用了下列所有的选项:
unused-function:遇到仅声明过但尚未定义的静态函数时发出警告。
unused-label:遇到声明过但不使用的标号的警告。
unused-parameter:从未用过的函数参数的警告。
unused-variable:在本地声明但从未用过的变量的警告。
unused-value:仅计算但从未用过的值得警告。
Format:检查对printf和scanf等函数的调用,确认各个参数类型和格式串中的一致。
implicit-int:警告没有规定类型的声明。
implicit-function-:在函数在未经声明就使用时给予警告。
char-subscripts:警告把char类型作为数组下标。这是常见错误,程序员经常忘记在某些机器上char有符号。
missing-braces:聚合初始化两边缺少大括号。
Parentheses:在某些情况下如果忽略了括号,编译器就发出警告。
return-type:如果函数定义了返回类型,而默认类型是int型,编译器就发出警告。同时警告那些不带返回值的 return语句,如果他们所属的函数并非void类型。
sequence-point:出现可疑的代码元素时,发出报警。
Switch:如果某条switch语句的参数属于枚举类型,但是没有对应的case语句使用枚举元素,编译器就发出警告(在switch语句中使用default分支能够防止这个警告)。超出枚举范围的case语句同样会导致这个警告。
strict-aliasing:对变量别名进行最严格的检查。
unknown-pragmas:使用了不允许的#pragma。
Uninitialized:在初始化之前就使用自动变量。
(不要被它的表面意思迷惑,下面是使用-Wall选项的时候没有生效的一些警告项,而且还有-Wextra)
cast-align:一旦某个指针类型强制转换时,会导致目标所需的地址对齐边界扩展,编译器就发出警告。例如,某些机器上只能在2或4字节边界上访问整数,如果在这种机型上把char *强制转换成int *类型, 编译器就发出警告。
sign-compare:将有符号类型和无符号类型数据进行比较时发出警告。
missing-prototypes:如果没有预先声明函数原形就定义了全局函数,编译器就发出警告。即使函数定义自身提供了函数原形也会产生这个警告。这样做的目的是检查没有在头文件中声明的全局函数。
Packed:当结构体带有packed属性但实际并没有出现紧缩式给出警告。
Padded:如果结构体通过充填进行对齐则给出警告。
unreachable-code:如果发现从未执行的代码时给出警告。
Inline:如果某函数不能内嵌(inline),无论是声明为inline或者是指定了-finline-functions 选项,编译器都将发出警告。
disabled-optimization:当需要太长时间或过多资源而导致不能完成某项优化时给出警告。
上面是使用-Wall选项时没有生效,但又比较常用的一些警告选项。
--------
-Wextra
但不要被-Wall的名字迷惑,它并没有开启所有告警,-Wextra用于启用一些未由-Wall启用的额外警告标志(此选项过去称为-W,旧名称仍然受支持,但更新的名称更具描述性。)
-Wclobbered
-Wcast-function-type
-Wempty-body
-Wignored-qualifiers 如果函数的返回类型具有类型限定符(如const,则发出警告。对于ISO C这样的类型限定符没有效果,因为函数返回的值不是左值。对于C++来说,警告只是针对标量类型或void发出的。 ISO C禁止在函数定义上使用合格的void返回类型,所以这种返回类型总是会在没有这个选项的情况下收到警告。
-Wimplicit-fallthrough=3
-Wmissing-field-initializers
-Wmissing-parameter-type (C only)
-Wold-style-declaration (C only)
-Woverride-init
-Wsign-compare (C only)
-Wtype-limits 由于数据类型范围有限而导致比较始终为真或始终为false,但不警告常量表达式。例如,警告如果将一个无符号变量与<或与0进行比较>=
-Wuninitialized
-Wshift-negative-value (in C++03 and in C99 and newer)
-Wunused-parameter (only with -Wunused or -Wall)
-Wunused-but-set-parameter (only with -Wunused or -Wall)
选项-Wextra还会打印以下情况的警告消息:
指针与整数零与<,<=,>或>=。
(仅限C ++)枚举器和非枚举器都出现在条件表达式中。
(仅限C ++)不明确的虚拟基础。
(仅限C ++)为已声明为register的数组下标。
(仅限C ++)取得已声明register的变量的地址。
(仅限C ++)基类不在派生类的复制构造函数中初始化。
--------
将告警转为错误
-Werror:所有告警当错误报
将所有的警告当成错误进行处理。一旦加上有警告就编译不通过。除非特别严格,一般不会使用。
-Werror= 将指定的警告转换为错误。
Format:检查对printf和scanf等函数的调用,确认各个参数类型和格式串中的一致。
printf("%d %d",1);
error: format '%d' expects a matching 'int' argument [-Werror=format]
反过来:
-Wno-error取消编译选项-Werror
实例:
假设使用了一个人的代码A目录,里面有一个-Werror的选项,把所有的警告当做错误;又使用了另一个人的代码B目录,里面存在一堆Warning。这样当把它们合在一起编译的时候,A中的-Werror选项会导致B的代码编译不过。但又不想去修改B的代码,怎么办?
方法是,先add_subdirectory(A),之后加上一句
set(CMAK_CXX_FLAGS "${CMAK_CXX_FLAGS} -Wno-error")
-Wno-这个前缀,就是用来取消一个编译选项的,然后再add_subdirectory(B)
其他告警项
-Wfatal-errors 发生第一个错误时中止编译
在发生第一个错误时中止编译。
-Wchkp
警告由指针界限检查器( -fcheck-pointer-bounds )发现的无效内存访问。
-Wno-coverage-mismatch
如果使用-fprofile-use选项时反馈配置文件不匹配,则警告。如果在使用-fprofile-gen编译和使用-fprofile-use编译时源文件发生更改,则具有配置文件反馈的文件可能无法与源文件匹配,并且GCC无法使用配置文件反馈信息。默认情况下,此警告已启用并被视为错误。-Wno-coverage-mismatch可用于禁用警告或-Wno-error=coverage-mismatch可用于禁用该错误。禁用此警告的错误可能会导致代码质量不佳,并且仅在非常小的更改情况下才有用,例如修复现有代码库的错误。不建议完全禁用该警告。
-Wno-cpp 禁止#warning指令发出的警告消息。
(仅限于Objective-C,C++,Objective-C++和Fortran)
禁止#warning指令发出的警告消息。
-Wshadow
当一个局部变量遮盖住了另一个局部变量,或者全局变量时,给出警告。很有用的选项,建议打开。-Wall 并不会打开此项。
-Wpointer-arith
对函数指针或者void *类型的指针进行算术操作时给出警告。也很有用。-Wall并不会打开此项。
-Wcast-qual
当强制转化丢掉了类型修饰符时给出警告。-Wall并不会打开此项。
-Waggregate-return
如果定义或调用了返回结构体或联合体的函数,编译器就发出警告。
-Winline
无论是声明为 inline 或者是指定了-finline-functions选项,如果某函数不能内联,编译器都将发出警告。如果你的代码含有很多 inline 函数的话,这是很有用的选项。
-Wundef
当一个没有定义的符号出现在 #if 中时,给出警告。
-Wredundant-decls
如果在同一个可见域内某定义多次声明,编译器就发出警告,即使这些重复声明有效并且毫无差别。
-Wstrict-prototypes
如果函数的声明或定义没有指出参数类型,编译器就发出警告。很有用的警告。
-Wctor-dtor-privacy(C++ only)
当一个类没有用时给出警告。因为构造函数和析构函数会被当作私有的。
-Wnon-virtual-dtor(C++ only)
当一个类有多态性,而又没有虚析构函数时,发出警告。-Wall会开启这个选项。
-Wreorder(C++ only)
如果代码中的成员变量的初始化顺序和它们实际执行时初始化顺序不一致,给出警告。
-Wno-deprecated(C++ only)
使用过时的特性时不要给出警告。
-Woverloaded-virtual(C++ only)
如果函数的声明隐藏住了基类的虚函数,就给出警告。
-Winit-self(C, C++, Objective-C and Objective-C++ only)
警告使用自己初始化的未初始化变量。 请注意,此选项只能与-Wuninitialized选项一起使用。
例如,只有在指定-Winit-self时,GCC才会在下面的代码片段中提醒i未初始化:
int f(){
int i = i;
回报我;
}
此警告由C++中的-Wall启用。
……
作用顺序和覆盖
具体的选项优先于不特定的选项,与命令行中的位置无关。对于相同特征的选项,最后一个生效。更多的WarningFlags可见此处。
参考来源:
GCC警告选项汇总
GCC(警告、优化以及调试选项)
GCC由cpp(预处理器)、gcc(C编译器)、g++(C++编译器)、binutils(Binary Utilities二进制工具)等工具组成。binutils是辅助gcc的主要软件,常用的工具有:as(汇编器)、ld(链接器)、ar(ar工具)等等。
在各大发行版本Linux中,其自带的gcc安装源版本都比较老旧,我所使用CentOS 6系统中,gcc版本只有4.4.7,而debian 7的是4.7。最近要安装的一些软件要求gcc 4.9+,只能手动升级gcc。
下载最新版本gcc
下载的是5.2,可以从最近的下载镜像,由于gcc本身比较大,所以下载会需要一些时间。如果下载很慢,也可以到gcc官网查找较快的下载镜像。gcc安装文件下载完成后,执行以下命令解压文件:
tar -xjf gcc-5.2.0.tar.bz2
下载gcc依赖文件和库
解压完成后,执行以下命令进入工作目录:
cd gcc-5.2.0
执行download_prerequisites脚本,下载gcc依赖文件和库:
./contrib/download_prerequisites
download_prerequisites脚本,会下载安装gcc所需的mpfr、gmp和mpc文件,下面是目前下载的版本。
./contrib/download_prerequisites
ftp://gcc.gnu.org/pub/gcc/infrastructure/mpfr-2.4.2.tar.bz2
ftp://gcc.gnu.org/pub/gcc/infrastructure/gmp-4.3.2.tar.bz2
ftp://gcc.gnu.org/pub/gcc/infrastructure/mpc-0.8.1.tar.gz
ftp://gcc.gnu.org/pub/gcc/infrastructure/isl-0.14.tar.bz2
配置安装gcc
可以建立一个输出目录,编译时所有生成的中间文件都放到该目录下:
mkdir gcc-temp
工作目录切换至输出目录,并在其中执行配置和安装:
cd gcc-temp
当然也可以在其解压目录中直接编译,执行configure配置安装文件:
./configure --enable-checking=release --enable-languages=c,c++ --disable-multilib --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
配置完成后,执行以下命令,编译gcc:
make -j 8
注意:编译gcc时间较长,在dell i5台式机上用时1小时。如果要更快一点,机器又支持超线程的话:make后面跟 -jN,-j 后面的数值是线程数,一般是 CPU 核心数的 2 倍。假如是4核心 CPU,则使用 -j8。configure 参数中,不指定 build、host、target参数,将显示unknown。
编译完成后,安装gcc:
make install
安装完成后还需要替换系统默认的gcc,执行以下命令,查找5.2版本的安装文件:
ls /usr/local/bin | grep gcc
输出如下:
gcc
gcc-ar
gcc-nm
gcc-ranlib
x86_64-unknown-linux-gnu-gcc
x86_64-unknown-linux-gnu-gcc-5.2.0 //就是这个
x86_64-unknown-linux-gnu-gcc-ar
x86_64-unknown-linux-gnu-gcc-nm
x86_64-unknown-linux-gnu-gcc-ranlib
debian执行升级命令:
/usr/sbin/update-alternatives --install /usr/bin/gcc gcc /usr/local/bin/x86_64-unknown-linux-gnu-gcc-5.2.0 52
updatedb && sudo ldconfig
locate gcc
下面将验证安装
查看gcc版本:
gcc -v
查看g++版本:
g++ -v
或使用which gcc查看gcc安装目录,在安装目录下执行-v命令。例如,安装目录为/usr/local/bin/gcc:
/usr/local/bin/gcc -v
如果输出中有类似以下行,说明安装成功:
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-linux-gnu/5.2.0/lto-wrapper
目标:x86_64-linux-gnu
配置为:./configure --enable-checking=release --enable-languages=c,c++ --disable-multilib --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
线程模型:posix
gcc 版本 5.2.0 (GCC)
可能出现的问题:
1、/usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found
编译程序或运行其它程序时,时不时会出现类似/usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found的问题。这是因为升级gcc时,生成的动态库没有替换老版本gcc的动态库导致的,将gcc最新版本的动态库替换系统中老版本的动态库即可解决。
检查动态库:
strings /usr/lib64/libstdc++.so.6 | grep GLIBC
输出结果如下:
GLIBCXX_3.4
GLIBCXX_3.4.1
GLIBCXX_3.4.2
GLIBCXX_3.4.3
GLIBCXX_3.4.4
GLIBCXX_3.4.5
GLIBCXX_3.4.6
GLIBCXX_3.4.7
GLIBCXX_3.4.8
GLIBCXX_3.4.9
GLIBCXX_3.4.10
GLIBCXX_3.4.11
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBCXX_FORCE_NEW
GLIBCXX_DEBUG_MESSAGE_LENGTH
从以上输出可以看出,gcc的动态库还是旧版本的。说明出现这些问题,是因为升级gcc时,生成的动态库没有替换老版本gcc的动态库。 执行以下命令,查找编译gcc时生成的最新动态库:
find / -name "libstdc++.so*"
输出如下:
/home/freeoa/gcc-temp/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so
/home/freeoa/gcc-temp/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
/home/freeoa/gcc-temp/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.21 //最新动态库
……
/home/freeoa/gcc-temp是升级gcc时的输出目录。将上面的最新动态库libstdc++.so.6.0.21复制到/usr/lib64目录下:
cp /home/freeoa/gcc-temp/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.21 /usr/lib64
复制后,修改系统默认动态库的指向,即:重建默认库的软连接。
切换工作目录至/usr/lib64:
cd /usr/lib64
删除原来软连接:
rm -rf libstdc++.so.6
将默认库的软连接指向最新动态库:
ln -s libstdc++.so.6.0.21 libstdc++.so.6
默认动态库升级完成。重新运行以下命令检查动态库:
strings /usr/lib64/libstdc++.so.6 | grep GLIBC
CentOS 6.x通过yum安装高版本Gcc
CentOS 6 的gcc的版本是 4.4,很多时候在编译安装软件都需要高版本的 GCC,否则就会报错。需要注意的是,在编译安装 GCC 之前,系统里必须先要通过 yum 安装老版本的 GCC 和依赖库。如果是在 x86_64 系统下编译的话,还需要安装 libgcc.i686 glibc-devel.i686 才行。编译安装 GCC 内存不小于 1GB,Swap 不小于 1GB,硬盘最低不小于 10GB,否则极有可能会中途报错退出。
gcc 4.8 安装
curl -Lks http://www.hop5.in/yum/el6/hop5.repo > /etc/yum.repos.d/hop5.repo
yum install gcc gcc-g++
gcc --version
gcc 4.9 安装
yum install centos-release-scl
yum install devtoolset-3-toolchain
scl enable devtoolset-3 bash
gcc --version
gcc 5.2 安装
yum install centos-release-scl
yum install devtoolset-4-toolchain
scl enable devtoolset-4 bash
gcc --version
scl enable devtoolset-3 bash只是临时覆盖系统原有的GCC引用,如果想永久覆盖,可在root/.bashrc文件中添加source /opt/rh/devtoolset-3/enable
Gcc仅仅作为真实的编译器和链接器的入口。它会在需要的时候调用其它组件(预处理器、汇编器、链接器),并且会传一些额外的参数给编译器和连接器。输入文件的类型和传给gcc的参数决定了gcc调用哪些组件。其所能识别的文件扩展名如下:
.c C语言文件
.i 预处理后的C语言文件
.C、.cc、.cp、.cpp、.c++、.cxx C++语言文件
.ii 预处理后的C++语言文件
.S 汇编文件
.s 预处理后的汇编文件
.o 编译后的目标文件
.a 目标文件的静态链接库(链接时使用)
.so 目标文件的动态链接库(链接、运行时使用)
编译过程示意图:

GCC编译选项
对Linux开发人员来讲,GCC给出的警告信息是很有价值的,它们不仅可以帮助程序员写出更加健壮的程序,而且还是跟踪和调试程序的有力工具。建议在用GCC编译源代码时始终带上-Wall选项,养成良好的习惯。
-Wall显示所有的警告信息。
-Wall选项可以打开所有类型的语法警告,以便于确定程序源代码是否是正确的,并且尽可能实现可移植性。
-pedantic以 ANSI/ISO C 标准列出的所有警告。
当GCC在编译不符合 ANSI/ISO C 语言标准的源代码时,如果在编译指令中加上了-pedantic选项,那么源程序中使用了扩展语法的地方将产生相应的警告信息。如果代码中的成员变量的初始化顺序和它们实际执行时初始化顺序不一致,给出警告。
----------------
常用选项
--help
--target-help
显示 gcc 帮助说明。‘target-help’是显示目标机器特定的命令行选项。
--version
显示 gcc 版本号和版权信息。
-o outfile
输出到指定的文件。
-x language
指明使用的编程语言。允许的语言包括:c c++ assembler none。‘none’意味着恢复默认行为,即根据文件的扩展名猜测源文件的语言。
-v
打印较多信息,显示编译器调用的程序。
-###
与 -v 类似,但选项被引号括住,并且不执行命令。
-E
仅作预处理,不进行编译、汇编和链接。
-S
仅编译到汇编语言,不进行汇编和链接。
-c
编译、汇编到目标代码,不进行链接。
-pipe
使用管道代替临时文件。
-combine
将多个源文件一次性传递给汇编器。
其他GCC选项更多有用的GCC选项:
-l library
-llibrary
进行链接时搜索名为library的库。
例子:$ gcc test.c -lm -o test
-Idir
把dir加入到搜索头文件的路径列表中。
例子:$ gcc test.c -I../inc -o test
-Ldir
把dir加入到搜索库文件的路径列表中。
例子:$ gcc -I/home/foo -L/home/foo -ltest test.c -o test
-Dname
预定义一个名为name 的宏,值为1。
例子:$ gcc -DTEST_CONFIG test.c -o test
-Dname=definition
预定义名为name,值为definition 的宏。
-ggdb
-ggdblevel
为调试器 gdb 生成调试信息。level 可以为1,2,3,默认值为2。
-g
-glevel
生成操作系统本地格式的调试信息。-g 和 -ggdb 并不太相同,-g 会生成 gdb 之外的信息。level 取值同上。
-s
去除可执行文件中的符号表和重定位信息。用于减小可执行文件的大小。
-M
告诉预处理器输出一个适合make的规则,用于描述各目标文件的依赖关系。对于每个 源文件,预处理器输出 一个make规则,该规则的目标项(target)是源文件对应的目标文件名,依赖项(dependency)是源文件中 `#include引用的所有文件。生成的规则可 以是单行,但如果太长,就用`\'-换行符续成多行。规则 显示在标准输出,不产生预处理过的C程序。
-C
告诉预处理器不要丢弃注释。配合`-E'选项使用。
-P
告诉预处理器不要产生`#line'命令。配合`-E'选项使用。
-static
在支持动态链接的系统上,阻止连接共享库。该选项在其它系统上无效。
-nostdlib
不连接系统标准启动文件和标准库文件,只把指定的文件传递给连接器。
优化项|优化等级
-O0禁止编译器进行优化。默认为此项。
-O1尝试优化编译时间和可执行文件大小。
-O2更多的优化,会尝试几乎全部的优化功能,但不会进行“空间换时间”的优化方法。
-O3在O2的基础上再打开一些优化选项:-finline-functions,-funswitch-loops 和 -fgcse-after-reload。
-Os对生成文件大小进行优化。它会打开 -O2 开的除了会那些增加文件大小的全部选项。
gcc默认提供了5级优:
-O0:无优化(默认)。
-O和-O1:使用能减少目标文件大小以及执行时间并且不会使编译时间明显增加的优化,在编译大型程序的时候会显著增加编译时内存的使用。
-O2:包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化,编译器不执行循环展开以及函数内联。此选项将增加编译时间和目标文件的执行性能。
-Os:专门优化目标文件大小,执行所有的不增加目标文件大小的-O2优化选项。并且执行专门减小目标文件大小的优化选项。
-O3:打开所有-O2的优化选项并且增加 -finline-functions,-funswitch-loops,-fpredictive-commoning,-fgcse-after-reload and -ftree-vectorize优化选项。
--------
Debug选项:
在gcc编译源代码时指定-g选项可以产生带有调试信息的目标代码,它可以为多个不同平台上帝不同调试器提供调试信息,默认gcc产生的调试信息是为gdb使用的,可以使用-gformat指定要生成的调试信息的格式以提供给其他平台的其他调试器使用。常用的格式有:
-ggdb:生成gdb专用的调试信息,使用最适合的格式(DWARF2,stabs等)会有一些gdb专用的扩展,可能造成其他调试器无法运行.
-gstabs:使用 stabs格式,不包含gdb扩展,stabs常用于BSD系统的DBX调试器.
-gcoff:产生COFF格式的调试信息,常用于System V下的SDB调试器;
-gxcoff:产生XCOFF格式的调试信息,用于IBM的RS/6000下的DBX调试器;
-gdwarf-2:产生DWARF version2 的格式的调试信息,常用于IRIXX6上的DBX调试器.GCC会使用DWARF version3的一些特性.
可以指定调试信息的等级,在指定的调试格式后面加上等级,如:-ggdb 2等,0代表不产生调试信息.在使用-gdwarf-2时因为最早的格式为-gdwarf2会造成混乱,所以要额外使用一个-glevel来指定调试信息的等级,其他格式选项也可以另外指定等级.
gcc可以使用-p选项指定生成信息以供porf使用。
--------
其他选项
-finline-functions
把所有简单的函数内联进调用者。编译器会探索式地决定哪些函数足够简单,值得做这种内联。
-fstrict-aliasing
施加最强的别名规则(aliasing rules)。
标准Standard
-ansi
支持符合ANSI标准的C程序。这样就会关闭GNU C中某些不兼容ANSI C的特性。
-std=c89 指明使用标准 ISO C90 作为标准来编译程序。
-std=c99 指明使用标准 ISO C99 作为标准来编译程序。
-std=c++98 指明使用标准 C++98 作为标准来编译程序。
-std=gnu9x 使用 ISO C99 再加上 GNU 的一些扩展。
-fno-asm
不把asm, inline或typeof当作关键字,因此这些词可以用做标识符。用 __asm__、__inline__和__typeof__能够替代它们。 `-ansi' 隐含声明了`-fno-asm'。
-fgnu89-inline
告诉编译器在 C99 模式下看到 inline 函数时使用传统的 GNU 句法。
C options
-fsigned-char
把char定义为有符号类型,如同signed char
-funsigned-char
把char定义为无符号类型,如同unsigned char。
-traditional
尝试支持传统C编译器的某些方面。详见GNU C手册。
-fno-builtin
-fno-builtin-function
不接受没有 __builtin_ 前缀的函数作为内建函数。
-trigraphs
支持ANSI C的三联符( trigraphs)。`-ansi'选项隐含声明了此选项。
-fsigned-bitfields
-funsigned-bitfields
如果没有明确声明`signed'或`unsigned'修饰符,这些选项用来定义有符号位域或无符号位域。缺省情况下,位域是有符号的,因为它们继承的基本整数类型,如int,是有符号数。
-fno-strict-aliasing
-fstrict-aliasing表示启用严格别名规则,-fno-strict-aliasing表示禁用严格别名规则,当gcc的编译优化参数为-O2、-O3和-Os时,默认会打开-fstrict-aliasing。
--------
fPIC选项
-fPIC的作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
gcc -shared -fPIC -o 1.so 1.c
fPIC参数使.so文件的代码段变为真正意义上的共享;如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy。每个copy都不一样,取决于这个.so文件代码段和数据段内存映射的位置。
也就是不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的(因为它里面的代码并不是位置无关代码)。如果被多个应用程序共同使用,那么它们必须每个程序维护一份.so的代码副本了(因为.so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。我们总是用fPIC来生成so,也从来不用fPIC来生成.a;fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目。
因此,不用fPIC编译so并不总是不好。如果满足以下4个需求/条件:
1.该库可能需要经常更新
2.该库需要非常高的效率(尤其是有很多全局量的使用时)
3.该库并不很大
4.该库基本不需要被多个应用程序共享
如果用没有加这个参数的编译后的共享库,也可以使用的话,可能是两个原因:
1.gcc默认开启-fPIC选项
2.loader使你的代码位置无关
从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。参见其手册页中有如下的描述:
-shared
Produce a shared object which can then be linked with other objects to form an executable. Not all systems support this option. For predictable results, you must also specify the same set of options that were used to generate code (`-fpic', `-fPIC', or model suboptions) when you specify this option.(1)
-fPIC 使用后会生成 PIC 代码,.so 要求为 PIC,以达到动态链接的目的,否则无法实现动态链接。
non-PIC 与 PIC 代码的区别主要在于 access global data, jump label 的不同。
比如一条 access global data 的指令,
non-PIC 的形势是:ld r3, var1
PIC 的形式则是:ld r3, var1-offset@GOT,意思是从 GOT 表的 index 为var1-offset 的地方处指示的地址处装载一个值,即var1-offset@GOT处的4个 byte 其实就是 var1 的地址。这个地址只有在运行的时候才知道,是由 dynamic-loader(ld-linux.so) 填进去的。
再比如 jump label 指令
non-PIC 的形势是:jump printf,意思是调用 printf。
PIC 的形式则是:jump printf-offset@GOT,
意思是跳到 GOT 表的 index 为 printf-offset 的地方处指示的地址去执行,这个地址处的代码摆放在 .plt section,每个外部函数对应一段这样的代码,其功能是呼叫dynamic-loader(ld-linux.so) 来查找函数的地址(本例中是 printf),然后将其地址写到 GOT 表的 index 为 printf-offset 的地方,同时执行这个函数。这样,第2次呼叫 printf 的时候,就会直接跳到 printf 的地址,而不必再查找了。
GOT 是 data section,是一个 table,除专用的几个 entry,每个 entry 的内容可以再执行的时候修改;
PLT 是 text section,是一段一段的 code,执行中不需要修改。
每个 target 实现 PIC 的机制不同,但大同小异。比如 MIPS 没有 .plt,而是叫 .stub,功能和 .plt 一样。
可见,动态链接执行很复杂,比静态链接执行时间长;但极大的节省了存储空间与大小,PIC 和动态链接技术是计算机发展史上非常重要的一个里程碑。
gcc manul上面有说
-fpic If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC and 32k on the m68k and RS/6000. The 386 has no such limit.)
-fPIC If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.
关键在于GOT全局偏移量表里面的跳转项大小。
intel处理器应该是统一4字节,没有问题。
powerpc上由于汇编码或者机器码的特殊要求,所以跳转项分为短、长两种。
-fpic为了节约内存,在GOT里面预留了“短”长度;而-fPIC则采用了更大的跳转项。
GCC警告选项
警告不是错误的,但是有风险或表明可能有错误。
英文原文:Warning Options - Using the GNU Compiler Collection (GCC)
加上-Wall吧,gcc 默认不加参数的情况下连定义了返回值的函数没有返回值都不报错。
----------------
开启和关闭告警方法
1、-w (小写)禁止所有警告消息
2、以“-W”(大写)开头开启特定的警告
例如:
-Wreturn-type(返回值告警),
-Wsign-compare(有符号和无符号对比告警)
-Wall (除extra外的所有告警)
-Wextra (all外的其他告警)
如$ gcc -Wall test.c -o test
3、以“-Wno-”开头关闭特定的警告
例如:
-Wno-return-type (取消返回值告警)
-Wno-sign-compare(取消有符号和无符号对比告警)
如:$ gcc -Wall -Wno-unused test.c -o test
批量开启告警(即-Wall和-Wextra 批量开启的告警)
某些选项(如-Wall和-Wextra )会打开其他选项,例如-Wunused;这可能会启用其他选项,例如-Wunused-value。
--------
-Wall
该选项相当于同时使用了下列所有的选项:
unused-function:遇到仅声明过但尚未定义的静态函数时发出警告。
unused-label:遇到声明过但不使用的标号的警告。
unused-parameter:从未用过的函数参数的警告。
unused-variable:在本地声明但从未用过的变量的警告。
unused-value:仅计算但从未用过的值得警告。
Format:检查对printf和scanf等函数的调用,确认各个参数类型和格式串中的一致。
implicit-int:警告没有规定类型的声明。
implicit-function-:在函数在未经声明就使用时给予警告。
char-subscripts:警告把char类型作为数组下标。这是常见错误,程序员经常忘记在某些机器上char有符号。
missing-braces:聚合初始化两边缺少大括号。
Parentheses:在某些情况下如果忽略了括号,编译器就发出警告。
return-type:如果函数定义了返回类型,而默认类型是int型,编译器就发出警告。同时警告那些不带返回值的 return语句,如果他们所属的函数并非void类型。
sequence-point:出现可疑的代码元素时,发出报警。
Switch:如果某条switch语句的参数属于枚举类型,但是没有对应的case语句使用枚举元素,编译器就发出警告(在switch语句中使用default分支能够防止这个警告)。超出枚举范围的case语句同样会导致这个警告。
strict-aliasing:对变量别名进行最严格的检查。
unknown-pragmas:使用了不允许的#pragma。
Uninitialized:在初始化之前就使用自动变量。
(不要被它的表面意思迷惑,下面是使用-Wall选项的时候没有生效的一些警告项,而且还有-Wextra)
cast-align:一旦某个指针类型强制转换时,会导致目标所需的地址对齐边界扩展,编译器就发出警告。例如,某些机器上只能在2或4字节边界上访问整数,如果在这种机型上把char *强制转换成int *类型, 编译器就发出警告。
sign-compare:将有符号类型和无符号类型数据进行比较时发出警告。
missing-prototypes:如果没有预先声明函数原形就定义了全局函数,编译器就发出警告。即使函数定义自身提供了函数原形也会产生这个警告。这样做的目的是检查没有在头文件中声明的全局函数。
Packed:当结构体带有packed属性但实际并没有出现紧缩式给出警告。
Padded:如果结构体通过充填进行对齐则给出警告。
unreachable-code:如果发现从未执行的代码时给出警告。
Inline:如果某函数不能内嵌(inline),无论是声明为inline或者是指定了-finline-functions 选项,编译器都将发出警告。
disabled-optimization:当需要太长时间或过多资源而导致不能完成某项优化时给出警告。
上面是使用-Wall选项时没有生效,但又比较常用的一些警告选项。
--------
-Wextra
但不要被-Wall的名字迷惑,它并没有开启所有告警,-Wextra用于启用一些未由-Wall启用的额外警告标志(此选项过去称为-W,旧名称仍然受支持,但更新的名称更具描述性。)
-Wclobbered
-Wcast-function-type
-Wempty-body
-Wignored-qualifiers 如果函数的返回类型具有类型限定符(如const,则发出警告。对于ISO C这样的类型限定符没有效果,因为函数返回的值不是左值。对于C++来说,警告只是针对标量类型或void发出的。 ISO C禁止在函数定义上使用合格的void返回类型,所以这种返回类型总是会在没有这个选项的情况下收到警告。
-Wimplicit-fallthrough=3
-Wmissing-field-initializers
-Wmissing-parameter-type (C only)
-Wold-style-declaration (C only)
-Woverride-init
-Wsign-compare (C only)
-Wtype-limits 由于数据类型范围有限而导致比较始终为真或始终为false,但不警告常量表达式。例如,警告如果将一个无符号变量与<或与0进行比较>=
-Wuninitialized
-Wshift-negative-value (in C++03 and in C99 and newer)
-Wunused-parameter (only with -Wunused or -Wall)
-Wunused-but-set-parameter (only with -Wunused or -Wall)
选项-Wextra还会打印以下情况的警告消息:
指针与整数零与<,<=,>或>=。
(仅限C ++)枚举器和非枚举器都出现在条件表达式中。
(仅限C ++)不明确的虚拟基础。
(仅限C ++)为已声明为register的数组下标。
(仅限C ++)取得已声明register的变量的地址。
(仅限C ++)基类不在派生类的复制构造函数中初始化。
--------
将告警转为错误
-Werror:所有告警当错误报
将所有的警告当成错误进行处理。一旦加上有警告就编译不通过。除非特别严格,一般不会使用。
-Werror= 将指定的警告转换为错误。
Format:检查对printf和scanf等函数的调用,确认各个参数类型和格式串中的一致。
printf("%d %d",1);
error: format '%d' expects a matching 'int' argument [-Werror=format]
反过来:
-Wno-error取消编译选项-Werror
实例:
假设使用了一个人的代码A目录,里面有一个-Werror的选项,把所有的警告当做错误;又使用了另一个人的代码B目录,里面存在一堆Warning。这样当把它们合在一起编译的时候,A中的-Werror选项会导致B的代码编译不过。但又不想去修改B的代码,怎么办?
方法是,先add_subdirectory(A),之后加上一句
set(CMAK_CXX_FLAGS "${CMAK_CXX_FLAGS} -Wno-error")
-Wno-这个前缀,就是用来取消一个编译选项的,然后再add_subdirectory(B)
其他告警项
-Wfatal-errors 发生第一个错误时中止编译
在发生第一个错误时中止编译。
-Wchkp
警告由指针界限检查器( -fcheck-pointer-bounds )发现的无效内存访问。
-Wno-coverage-mismatch
如果使用-fprofile-use选项时反馈配置文件不匹配,则警告。如果在使用-fprofile-gen编译和使用-fprofile-use编译时源文件发生更改,则具有配置文件反馈的文件可能无法与源文件匹配,并且GCC无法使用配置文件反馈信息。默认情况下,此警告已启用并被视为错误。-Wno-coverage-mismatch可用于禁用警告或-Wno-error=coverage-mismatch可用于禁用该错误。禁用此警告的错误可能会导致代码质量不佳,并且仅在非常小的更改情况下才有用,例如修复现有代码库的错误。不建议完全禁用该警告。
-Wno-cpp 禁止#warning指令发出的警告消息。
(仅限于Objective-C,C++,Objective-C++和Fortran)
禁止#warning指令发出的警告消息。
-Wshadow
当一个局部变量遮盖住了另一个局部变量,或者全局变量时,给出警告。很有用的选项,建议打开。-Wall 并不会打开此项。
-Wpointer-arith
对函数指针或者void *类型的指针进行算术操作时给出警告。也很有用。-Wall并不会打开此项。
-Wcast-qual
当强制转化丢掉了类型修饰符时给出警告。-Wall并不会打开此项。
-Waggregate-return
如果定义或调用了返回结构体或联合体的函数,编译器就发出警告。
-Winline
无论是声明为 inline 或者是指定了-finline-functions选项,如果某函数不能内联,编译器都将发出警告。如果你的代码含有很多 inline 函数的话,这是很有用的选项。
-Wundef
当一个没有定义的符号出现在 #if 中时,给出警告。
-Wredundant-decls
如果在同一个可见域内某定义多次声明,编译器就发出警告,即使这些重复声明有效并且毫无差别。
-Wstrict-prototypes
如果函数的声明或定义没有指出参数类型,编译器就发出警告。很有用的警告。
-Wctor-dtor-privacy(C++ only)
当一个类没有用时给出警告。因为构造函数和析构函数会被当作私有的。
-Wnon-virtual-dtor(C++ only)
当一个类有多态性,而又没有虚析构函数时,发出警告。-Wall会开启这个选项。
-Wreorder(C++ only)
如果代码中的成员变量的初始化顺序和它们实际执行时初始化顺序不一致,给出警告。
-Wno-deprecated(C++ only)
使用过时的特性时不要给出警告。
-Woverloaded-virtual(C++ only)
如果函数的声明隐藏住了基类的虚函数,就给出警告。
-Winit-self(C, C++, Objective-C and Objective-C++ only)
警告使用自己初始化的未初始化变量。 请注意,此选项只能与-Wuninitialized选项一起使用。
例如,只有在指定-Winit-self时,GCC才会在下面的代码片段中提醒i未初始化:
int f(){
int i = i;
回报我;
}
此警告由C++中的-Wall启用。
……
作用顺序和覆盖
具体的选项优先于不特定的选项,与命令行中的位置无关。对于相同特征的选项,最后一个生效。更多的WarningFlags可见此处。
参考来源:
GCC警告选项汇总
GCC(警告、优化以及调试选项)