gcc 入门

    博客分类: 笔记 阅读次数:

gcc 入门

gcc 和 g++ 的区别

  1. gcc 是 GCC 编译器的通用编译指令。根据文件的后缀名,gcc 指令可以自行判断出编程语言的类别,比如 xxx.c 默认以 C 语言程序的方式编译,xxx.cpp 默认以 C++ 语言程序的方式编译

    当然,gcc 指令也提供了手动指定代表编译方式的接口,即使用 -x 选项。 gcc -xc++ xxx.c 即表示以 C++ 代码的方式编译 xxx.cpp 文件。

    g++ 指令无论文件的后缀名是什么,都一律按照编译 C++ 代码的方式编译。

  2. 单纯的 gcc 命令无法自动链接 C++ 标准库文件,需手动为其添加 -lstdc++ -shared-libgcc 选项。

    g++ 指令可以认为等同于 gcc -xc++ -lstdc++ -shared-libgcc

  3. 其他区别详见:gcc和g++是什么,有什么区别? (biancheng.net)

编译过程

一个 gcc 的编译过程大致如上图。可以分为以下几步:预处理、编译、汇编、链接

编译过程.png

预处理

预处理规则

预处理作用:确定宏或者头文件包含是否正确。当我们无法确认时,可以通过预处理后的文件来确定问题。

预编译用法:使用 gcc-E 选项(默认情况下 gcc -E 只会将预处理的结果输出到屏幕上,并不会自动保存到某个文件。因此该指令往往会和 -o 选项连用)

gcc -E hello.c -o hello.i

经过预编译后,会得到一个 .i 文件,这个文件不包含任何宏定义,所有的宏都已经被展开,并且包含的头文件也被插入到 .i 文件中。

预处理过程常用的编译选项:GCC -E选项:对源程序做预处理操作 (biancheng.net)

编译

编译过程就是把预处理完的文件进行一些列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。

用法:使用 gcc-S 选项

gcc -S hello.i -o hello.s

注意:gcc -S 也处理的不必须是经过预处理的 .i 文件,其功能是“将指定文件处理至编译阶段结束”,因此其也可以操作源代码文件:

gcc -S demo.c -o demo.s

如果想提高汇编代码的可读性,可以借助 -fverbose-asm 选项,GCC 编译器会为汇编代码添加必要的注释:

gcc -S demo.c -fverbose-asm

汇编

汇编是将汇编代码转变成机器可以执行的指令。大部分汇编语句对应一条机器指令,有的汇编语句对应多条机器指令。

用法:可以调用汇编器 as 或者使用 gcc-c 选项。

as hello.s -o hello.o
gcc -c hello.s -o hello.o

经过汇编会得到目标文件 .o(Object File)

注意,和 gcc -S 类似,gcc -c 选项并非只能用于加工 .s 文件。事实上,-c 选项是“令 GCC 编译器将指定文件加工至汇编阶段,但不执行链接操作”。

链接

链接过程,是一个比较复杂的过程。链接过程主要包括了以下步骤:

符合决议有时候又叫做符号绑定(Symbol Binding)、名称绑定(Name Binding)。”决议“更倾向于静态链接,”绑定“更倾向于动态链接。我们通过 gcc -v 可以打印编译的详细信息。

关于链接的详细过程参见:彻底理解链接器:一,概念 - SegmentFault 思否

总结

现代版的 gcc 把预编译和编译两个步骤合并成一个步骤,使用一个叫做 cc1 的程序来完成这两个步骤。对于 C++ 来说是 cc1plus。所以实际上 gcc 这个命令只是这些后台程序的包装,它会根据不同的参数要求去调用预编译程序 cc1、汇编器 as、链接器 ld

GCC 编译选项

编译选项 功 能
-E 预处理指定的源文件,不进行编译。
-S 编译指定的源文件,但是不进行汇编。
-c 编译、汇编指定的源文件,但是不进行链接。
-o 指定生成文件的文件名。
-g 生成调试信息。GNU 调试器可利用该信息。
-Olevel -O0 不进行优化处理;-O / -O1 优化生成代码;-O2 进一步优化。-O3-O2 更进一步优化,包括 inline 函数。
-w/-Wall 不生成任何警告信息/ 生成所有警告信息
-Dmacro 定义宏,默认宏的内容是1。gcc -DDEBUG 常用来选择开启DEBUG宏调试;gcc -DNAME=Peter 定义宏并指定内容。
-Umacro 取消宏定义,作用正好和-D相反
-Idir 把 dir 加到头文件的搜索路径中,而且 gcc 会在搜索标准头文件之前先搜索 dir
-Ldir 把 dir 加到库文件的搜索路径中,而且 gcc 会在搜索标准库文件之前先搜索 dir
-llib 指定链接环节中可以调用的库文件,比如 -lstdc++
-ansi 对于 C 语言程序来说,其等价于 -std=c90;对于 C++ 程序来说,其等价于 -std=c++98。这一选项将禁止 GNU C 的某些特色。
-std= 手动指令编程语言所遵循的标准,例如 c89、c90、c++98、c++11 等。
-wl  
-fPIC 使编译阶段产生位置无关代码(Position-Independent Code)。
-fpermissive 该选项会将不一致代码的诊断从错误降级为警告。最好不要使用,因为会降低对于代码检查的严格性。

-fPIC

-fPIC产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

gcc -shared -fPIC -o 1.so 1.c

如果不加-fPIC,则加载 .so 文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 .so 文件代码段的进程在内核里都会生成这个 .so 文件代码段的 copy。每个 copy 都不一样,取决于这个 .so 文件代码段和数据段内存映射的位置。

-l 常用的库

Linux 的库命名是一致的,一般为 libxxx.solibxxx.alibxxx.la,那么你要链接某个库就用 -lxxx 即可。常用 -l 指定的一些库:

-I,-L 和 -l 的区别

聊聊gcc参数中的-I, -L和-l_认知 行动 坚持-CSDN博客

GCC 的错误类型及对策

gcc给出的错误资讯一般可以分为四大类,下面我们分别讨论其产生的原因和对策。

语法错误

错误提示∶文件 source.c 中第 n 行有语法错误(syntex errror)。这种类型的错误,一般都是语法错误,应该仔细检查源代码文件,有时也需要对该文件所包含的头文件进行检查。有些情况下,一个很简单的语法错误,GCC 会给出一大堆错误,我们最主要的是要保持清醒的头脑,不要被其吓倒。

头文件错误

错误提示∶找不到头文件 head.h(Can not find include file head.h)。这类错误是源文件中的包含头文件有问题,可能的原因有头文件名错误、指定的头文件所在目录名错误等,也可能是错误地使用了双引号和尖括号。

档案库错误

错误提示∶连接程序找不到所需的函数库,例如∶ld: -lm: No such file or directory

这类错误是与目标文件相链接的函数库有错误,可能的原因是函数库名错误、指定的函数库所在目录名称错误等,检查的方法是使用 find 命令在可能的目录中寻找相应的函数库名,确定档案库及目录的名称并修改程序中及编译选项中的名称。

未定义符号

错误提示∶有未定义的符号(Undefined symbol)。这类错误是在连接过程中出现的,可能有两种原因∶

  1. 使用者自己定义的函数或者全局变量所在源代码文件,没有被编译、链接,或者干脆还没有定义,这需要使用者根据实际情况修改源程序,给出全局变量或者函数的定义
  2. 未定义的符号是一个标准的库函数,在源程序中使用了该库函数,而链接过程中还没有给定相应的函数库的名称,或者是该档案库的目录名称有问题,这时需要使用档案库维护命令 ar 检查我们需要的库函数到底位于哪一个函数库中,确定之后,修改 gcc 连接选项中的 -l-L 项。

参考资料

编译和链接:被隐藏的细节 - 知乎 (zhihu.com)

gcc和g++是什么,有什么区别? (biancheng.net)

彻底理解链接器:一,概念 - SegmentFault 思否

gcc及其选项详解-zhenhuaqin-ChinaUnix博客

gcc编译参数-fPIC的一些问题_老徐_新浪博客 (sina.com.cn)

聊聊gcc参数中的-I, -L和-l_认知 行动 坚持-CSDN博客