一、GCC简介
GCC(GNU Compiler Collection)是 GNU 工具链的主要组成部分,是一套以 GPL 和 LGPL 许可证发布的程序语言编译器自由软件,由 Richard Stallman 于 1985 年开始开发。
GCC 原名为 GNU C语言编译器,因为它原本只能处理 C 语言,但如今的 GCC 不仅可以编译 C、C++ 和 Objective-C,还可以通过不同的前端模块支持各种语言,包括 Java、Fortran、Ada、Pascal、Go 和 D 语言等等。
GCC 的编译过程可以划分为四个阶段:
-
预处理(Pre-Processing):生成 .i 的文件[预处理器cpp]
-
编译(Compiling): 将预处理后的文件转换成汇编语言, 生成文件 .s [编译器egcs]
-
汇编(Assembling): 由汇编变为目标代码(机器代码)生成 .o 的文件[汇编器as]
-
链接生成可执行文件(Linking) [链接器ld]
![](https://i-blog.csdnimg.cn/blog_migrate/3a1a67f168c7b118597534e4cbd178d9.png)
安装:
# 安装c编译器(linux需安装python-devel, gcc)
centos: yum install python-devel gcc
ubuntu: apt-get install build-essential
二、GCC使用
nist@zq-node2:~/test$ gcc --help
Usage: gcc [options] file...
Options:
-pass-exit-codes 从一个阶段以最高错误代码退出.
--help Display this information.
--target-help 显示特定于目标的命令行选项.
--help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...].
显示特定类型的命令行选项.
(使用 '-v --help' 显示子进程的命令行选项).
--version 显示编译器版本信息.
-dumpspecs 显示所有内置规范字符串.
-dumpversion 显示编译器的版本.
-dumpmachine 显示编译器的目标处理器.
-print-search-dirs 显示编译器搜索路径中的目录.
-print-libgcc-file-name 显示编译器配套库的名称.
-print-file-name=<lib> 显示库 <lib> 的完整路径.
-print-prog-name=<prog> 显示编译器组件 <prog> 的完整路径.
-print-multiarch 显示目标的规范化 GNU 三元组,用作库路径中的一个组件.
-print-multi-directory 显示 libgcc 版本的根目录.
-print-multi-lib 显示命令行选项和多个库搜索目录之间的映射.
-print-multi-os-directory 显示操作系统库的相对路径.
-print-sysroot 显示目标库目录.
-print-sysroot-headers-suffix 显示用于查找headers的sysroot后缀.
-Wa,<options> 将逗号分隔的 <options> 传递给汇编器(assembler).
-Wp,<options> 将逗号分隔的 <options> 传递给预处理器(preprocessor)
-Wl,<options> 将逗号分隔的 <options> 传递给链接器(linker)
-Xassembler <arg> 将 <arg> 传递给汇编器(assembler).
-Xpreprocessor <arg> 将 <arg> 传递给预处理器(preprocessor).
-Xlinker <arg> 将 <arg> 传递给链接器(linker).
-save-temps 不用删除中间文件.
-save-temps=<arg> 不用删除指定的中间文件.
-no-canonical-prefixes 在构建其他 gcc 组件的相对前缀时,不要规范化路径.
-pipe 使用管道而不是中间文件
-time 为每个子流程的执行计时.
-specs=<file> 使用 <file> 的内容覆盖内置规范.
-std=<standard> 假设输入源用于<standard>。
--sysroot=<directory> 使用<standard>作为headers和libraries的根目录.
-B <directory> 将 <directory> 添加到编译器的搜索路径.
-v 显示编译器调用的程序.
-### 与 -v 类似,但引用的选项和命令不执行.
-E 仅执行预处理(不要编译、汇编或链接).
-S 只编译(不汇编或链接).
-c 编译和汇编,但不链接.
-o <file> 指定输出文件. gcc 编译出来的文件默认是 a.out
-pie 创建一个动态链接、位置无关的可执行文件
-shared 创建共享库/动态库.
-g: 生成调试信息
-w: 不生成任何警告
-Wall: 编译时 显示Warning警告,但只会显示编译器认为会出现错误的警告
-x <language> 指定以下输入文件的语言。允许的语言包括:c c++汇编程序none“none”表示恢复到的默认行为根据文件的扩展名猜测语言。
Options starting with -g, -f, -m, -O, -W, or --param are automatically
passed on to the various sub-processes invoked by gcc. In order to pass
other options on to these processes the -W<letter> options must be used.
For bug reporting instructions, please see:
<file:///usr/share/doc/gcc-9/README.Bugs>.
-shared:
编译动态库时要用到
-pthread:
在Linux中要用到多线程时,需要链接pthread库
-fPIC:
作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),
则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意
位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
-fwrapv:
它定义了溢出时候编译器的行为——采用二补码的方式进行操作
-O参数
这是一个程序优化参数,一般用-O2就是,用来优化程序用的
-O2:
会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。
-O3: 在O2的基础上进行更多的优化
-Wall:
编译时 显示Warning警告,但只会显示编译器认为会出现错误的警告
-fno-strict-aliasing:
“-fstrict-aliasing”表示启用严格别名规则,“-fno-strict-aliasing”表示禁用严格别名规则,当gcc的编译优化参数为“-O2”、“-O3”和“-Os”时,默认会打开“-fstrict-aliasing”。
-I (大写的i):
是用来指定头文件目录
-I /home/hello/include表示将/home/hello/include目录作为第一个寻找头文件的目录,寻找的顺序是:/home/hello/include-->/usr/include-->/usr/local/include
-l:
-l(小写的 L)参数就是用来指定程序要链接的库,-l参数紧接着就是库名,把库文件名的头lib和尾.so去掉就是库名了,例如我们要用libtest.so库库,编译时加上-ltest参数就能用上了
2.1、gcc编译的四个步骤
预处理: gcc -E Test.c -o Test.i # -E选项,可以让编译器在预处理后停止,并输出预处理结果
编译: gcc -S Test.i -o Test.s # -S选项,表示在程序编译期间,将我们的代码编译成汇编语言。
汇编: gcc -c Test.s -o Test.o # -c选项,表示由汇编器负责将刚才的.s文件编译为目标文件,即计算机所能识别的序列。
链接生成可执行文件: gcc Test.o -o Test # 将刚才的Test.o文件与C标准输入输出库进行连接,最终生成程序Test可执行文件
2.2、多源文件的编译方法
[假设有两个源文件为test.c和testfun.c]
1. 多个文件一起编译
用法:
gcc testfun.c test.c -o test
作用:将testfun.c和test.c分别编译后链接成test可执行文件。
2. 分别编译各个源文件,之后对编译后输出的目标文件链接
用法:
gcc -c testfun.c -o testfun.o # 将testfun.c编译成testfun.o
gcc -c test.c -o test.o # 将test.c编译成test.o
gcc -o testfun.o test.o -o test # 将testfun.o和test.o链接成test
以上两种方法相比较,第一中方法编译时需要所有文件重新编译,而第二种方法可以只重新编译修改的文件,未修改的文件不用重新编译。
2.3、库文件连接
开发软件是需要依赖第三方函数库。
函数库实际上就是一些头文件(.h)和库文件(so、或lib、dll)的集合。
虽然Linux下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下;Windows所使用的库文件主要放在Visual Stido的目录下的include和lib,以及系统文件夹下。但也有的时候,我们要用的库不再这些目录下,所以GCC在编译时必须用自己的办法来查找所需要的头文件和库文件。
例如我们的程序test.c是在linux上使用c连接mysql,这个时候我们需要去mysql官网下载MySQL Connectors的C库,下载下来解压之后,有一个include文件夹,里面包含mysql connectors的头文件,还有一个lib文件夹,里面包含二进制so文件libmysqlclient.so
其中inclulde文件夹的路径是/usr/dev/mysql/include,lib文件夹是/usr/dev/mysql/lib
![](https://i-blog.csdnimg.cn/blog_migrate/1b80d0920f48cea327ff46a567afcf69.png)
静态库链接时搜索路径顺序:
-
ld会去找GCC命令中的参数-L
-
再找gcc的环境变量LIBRARY_PATH
-
再找内定目录 /lib、 /usr/lib、 /usr/local/lib 这是当初compile gcc时写在程序内的
动态链接时、执行时搜索路径顺序:
-
编译目标代码时指定的动态库搜索路径
-
环境变量LD_LIBRARY_PATH指定的动态库搜索路径
-
配置文件/etc/ld.so.conf中指定的动态库搜索路径
-
默认的动态库搜索路径/lib
-
默认的动态库搜索路径/usr/lib
有关环境变量:
-
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
-
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径
三、示例
3.1、阶段编译
假设有文件 hello.c,内容如下
:
#include <stdio.h>
int main(void)
{
printf("Hello, GetIoT\n");
return 0;
}
1、编译 hello.c,默认输出 a.out
:
gcc hello.c
编译 hello.c 并指定输出可执行文件为 hello
:
gcc hello.c -o hello
![](https://i-blog.csdnimg.cn/blog_migrate/fa3ca050b2a44badd3d2218d51eadc2a.png)
2、只执行预处理,输出 hello.i 源文件
:
gcc -E hello.c -o hello.i
3、只执行预处理和编译,输出 hello.s 汇编文件
:
gcc -S hello.c -o hello.s
也可以由 hello.i 文件生成 hello.s 汇编文件
:
gcc -S hello.i -o hello.s
4、只执行预处理、编译和汇编,输出 hello.o 目标文件
:
gcc -c hello.c -o hello.o
也可以由 hello.i 或 hello.s 生成目标文件 hello.o:
gcc -c hello.i -o hello.o
gcc -c hello.s -o hello.o
5、由 hello.o 目标文件链接成可执行文件 hello
gcc hello.o -o hello
3.2、使用静态库
创建一个 foo.c 文件,内容如下:
:
#include <stdio.h>
void foo(void)
{
printf("Here is a static library\n");
}
将 foo.c 编译成静态库 libfoo.a:
gcc -c foo.c # 生成 foo.o 目标文件
ar rcs libfoo.a foo.o # 生成 libfoo.a 静态库
![](https://i-blog.csdnimg.cn/blog_migrate/9a039e122cada9612d0a03de441b5410.png)
查看文件描述
:
$ file *
![](https://i-blog.csdnimg.cn/blog_migrate/fa4c547dbf4cf86342c288abcf67fc09.png)
修改 hello.c 文件,调用 foo 函数
:
#include <stdio.h>
void foo(void);
int main(void)
{
printf("Hello, GetIoT\n");
foo();
return 0;
}
编译 hello.c 并链接静态库 libfoo.a(加上 -static 选项)
:
gcc hello.c -static libfoo.a -o hello
也可以使用
-L
指定库的搜索路径,并使用
-l
指定库名)
:
gcc hello.c -static -L. -lfoo -o hello
![](https://i-blog.csdnimg.cn/blog_migrate/88cca283d5c9cdb58685bc75d0134303.png)
运行结果:
$ ./hello
![](https://i-blog.csdnimg.cn/blog_migrate/4c2318e20d0a79da28b3d0e650692662.png)
查看 hello 文件描述
:
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=f20be3e93d3c316877bbe4292f5ee7e06cf77f27, for GNU/Linux 3.2.0, not stripped
3.3、使用共享库
修改 foo.c 文件,内容如下
:
#include <stdio.h>
void foo(void)
{
printf("Here is a shared library\n");
}
将其编译为动态库/共享库(由于动态库可以被多个进程共享加载,所以需要使用
-fPIC 选项生成位置无关的代码
:
gcc foo.c -shared -fPIC -o libfoo.so
![](https://i-blog.csdnimg.cn/blog_migrate/2955419dfe8bfac88566d5409e4f94e5.png)
hello.c 代码无需修改,内容仍然如下
:
#include <stdio.h>
void foo(void);
int main(void)
{
printf("Hello, GetIoT\n");
foo();
return 0;
}
编译 hello.c 并链接共享库 libfoo.so
:
gcc hello.c libfoo.so -o hello
也可以使用 -L 和 -l 选项指定库的路径和名称
:
gcc hello.c -L. -lfoo -o hello
但是此时运行 hello 程序失败
:
$ ./hello./hello: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
原因是找不到 libfoo.so 共享库
:
$ ldd hello
linux-vdso.so.1 (0x00007fff5276d000)
libfoo.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcc90fa7000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcc911bd000)
这是因为 libfoo.so 并不在 Linux 系统的默认搜索目录中,解决办法是我们主动告诉系统,libfoo.so 共享库在哪里
。
方式一
:设置环境变量
LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$(pwd)
将 libfoo.so 所在的当前目录添加到
LD_LIBRARY_PATH
变量,再次执行 hello
$ ./hello
Hello, GetIoT
Here is a shared library
方式二
:使用 rpath 将共享库位置嵌入到程序
gcc hello.c -L. -lfoo -Wl,-rpath=`pwd` -o hello
rpath 即 run path,是种可以将共享库位置嵌入程序中的方法,从而不用依赖于默认位置和环境变量。这里在链接时使用
-Wl,-rpath=/path/to/yours
选项,
-Wl
会发送以逗号分隔的选项到链接器,注意逗号分隔符后面没有空格哦。
这种方式要求共享库必须有一个固定的安装路径,欠缺灵活性,不过如果设置了
LD_LIBRARY_PATH
,程序加载时也是会到相应路径寻找共享库的。
方式三
:
将 libfoo.so 共享库添加到系统路径
sudo cp libfoo.so /usr/lib/
执行程序
$ ./hello
Hello, GetIoT
Here is a shared library
此时,再次查看 hello 程序的共享库依赖
$ ldd hello
linux-vdso.so.1 (0x00007ffecfbb1000)
libfoo.so => /lib/libfoo.so (0x00007f3f3f1ad000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3f3efbb000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3f3f1d6000)
可以看到 libfoo.so 已经被发现了,其中
/lib 是 /usr/lib 目录的软链接
。