前言
-
GCC 仅仅是一个编译器,没有界面,必须在命令行模式下使用。通过 gcc 命令就可以将源文件编译成可执行文件。
-
一个C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)等4步才能变成可执行文件,通常使用“编译”统称这4个步骤。
一、gcc 编译四步骤
二、gcc编译常用参数
-I 指定头文件所在目录位置
-c 只做预处理,编译,汇编。得到二进制文件
-g 编译时添加调试文件,用于 gdb 调试
-Wall 显示所有警告信息
-D 向程序中“动态”注册宏定义
-l 指定动态库库名
-L 指定动态库路径
三、文件后缀名对应表
后缀名 | 类型 |
---|---|
.c | c源程序 |
.h | 预处理器文件 |
.cpp | c++源程序 |
.i | 预处理后的c文件 |
.ii | 预处理后的c++文件 |
.s | 汇编语言源程序 |
.o | 目标文件(Object file) |
.a | 静态链接库文件(linux) |
.so | 动态链接库文件(linux) |
.lib | 静态链接库文件(windows) |
.dll | 动态链接库文件(windows) |
四、预处理
预处理是读取 c 源程序,对其中的伪指令(以 # 开头的指令,也就是宏)和特殊符号进行“替代”处理;经过此处理,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,仍然是 C 文件,但内容有所不同。
预处理主要包括以下过程:
- 将所有的 #define 删除,并且展开所有的宏定义
- 处理所有的条件预编译指令,比如 #if #ifdef #elif #else #endif 等
- 处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置。
- 删除所有注释 “//”和”/* */”.
- 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
- 保留所有的 #pragma 编译器指令,因为编译器需要使用它们
下面我们将一端简单的C代码做示例。
/* --------普通的c代码----------*/
#include <stdio.h>
#define COUNTE 5
int main(int argc,char ** argv)
{
for(int i=0; i <= COUNTE; i++) {
printf("hello world!\n");
}
return 0;
}
然后经过预处理:
gcc -E hello.c -o hello.i
其注释被删除,且宏也被替代之后删除,如下图所示。
五、编译
编译程序所要作的工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
所用到的工具为 cc1(它的名字就是 cc1, x86 有自己的 cc1 命令, ARM 板也有自己的 cc1 命令)。
gcc -S hello.i -o hello.s
不同的编译器交叉编译同一个 hello.i 文件生成的汇编文件也不相同,这也是 C 语言可移植性的一种体现。
六、汇编
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个 C 语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。 目标文件由段组成。通常一个目标文件中至少有两个段:
- 代码段(文本段):该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般不可写;
- 数据段:主要存放程序中要用到的各种常量、全局变量、静态的数据。一般数据段都是可读,可写,可执行的;
在 Linux 系统上一般表现为 ELF 目标文件(OBJ 文件),用到的工具为 as。 x86 有自己的 as 命令, ARM 版也有自己的 as 命令,也可能是 xxxx-as(比如 armlinux-as)。
gcc -c hello.s -o hello.o
因为翻译成了计算机可以识别的二进制文件,因此乱码。
七、链接
汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。 链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体,也就是可执行程序。 根据开发人员指定的库函数的链接方式的不同,链接处理可分为两种:
- 静态链接
- 动态链接
用到的工具为 ld 或 collect2。
1、静态链接
静态链接的优点:
(1)装载速度很快,运行速度比动态链接快;
(2)只需要开发人员在开发机上有完整的.lib文件,不需要在用户机器上有完整的.lib文件,自完备
静态链接的缺点:
(1)可执行文件很大,并且相同代码很多,资源浪费
动态链接编译:
gcc hello.o -o hello -static
2、动态链接
动态链接的优点:
(1)可执行文件很小;
(2)适合大规模软件开发,开发过程耦合度小、独立,便于不同开发人员和开发组织开发;
(3)不同编程语言按照约定可以使用同一套.dll库;
动态链接的缺点:
(1)速度没有静态链接快;
(2)不具有自完备,如果用户机器中没有.dll文件,程序将无法运行并且报错
动态链接编译:
gcc hello.o -o hello
八、查看具体过程
编译程序时,加上 -v 选项就可以看到这几个步骤。比如:
gcc hello.c -o hello -v
可以看到如下的信息:
Gnep@lpvm:~/test$ gcc hello.c -o hello -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.3.0-1ubuntu1~22.04.1' --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-11-aYxV0E/gcc-11-11.3.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-aYxV0E/gcc-11-11.3.0/debian/tmp-gcn/usr --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04.1)
COLLECT_GCC_OPTIONS='-o' 'hello' '-v' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/11/cc1 -quiet -v -imultiarch x86_64-linux-gnu hello.c -quiet -dumpbase hello.c -dumpbase-ext .c -mtune=generic -march=x86-64 -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccJn089M.s
GNU C17 (Ubuntu 11.3.0-1ubuntu1~22.04.1) version 11.3.0 (x86_64-linux-gnu)
compiled by GNU C version 11.3.0, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.1, isl version isl-0.24-GMP
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/11/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/11/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/11/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
GNU C17 (Ubuntu 11.3.0-1ubuntu1~22.04.1) version 11.3.0 (x86_64-linux-gnu)
compiled by GNU C version 11.3.0, GMP version 6.2.1, MPFR version 4.1.0, MPC version 1.2.1, isl version isl-0.24-GMP
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: e13e2dc98bfa673227c4000e476a9388
COLLECT_GCC_OPTIONS='-o' 'hello' '-v' '-mtune=generic' '-march=x86-64'
as -v --64 -o /tmp/ccZUpiuk.o /tmp/ccJn089M.s
GNU汇编版本 2.38 (x86_64-linux-gnu) 使用BFD版本 (GNU Binutils for Ubuntu) 2.38
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-o' 'hello' '-v' '-mtune=generic' '-march=x86-64' '-dumpdir' 'hello.'
/usr/lib/gcc/x86_64-linux-gnu/11/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/11/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper -plugin-opt=-fresolution=/tmp/cc1e5NmP.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o hello /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/11/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/11 -L/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/11/../../.. /tmp/ccZUpiuk.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/11/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-o' 'hello' '-v' '-mtune=generic' '-march=x86-64' '-dumpdir' 'hello.'
可以看到很多输出结果,我们把其中的主要信息摘出来:
cc1 hello.c -o /tmp/ccJn089M.s # 预处理 + 编译
as -o /tmp/ccZUpiuk.o /tmp/ccJn089M.s # 汇编
collect2 -o hello Scrt1.o crti.o crtbeginS.o ccZUpiuk.o crtendS.o crtn.o # 链接
以上 3 个命令分别对应于编译步骤中的预处理 + 编译、汇编和链接, ld 被 collect2 调用来链接程序。预处理和编译被放在了一个命令( cc1)中进行的,可以把它再次拆分为以下两步:
cpp -o hello.i hello.c
cc1 hello.i -o /tmp/ccJn089M.s
我们不需要手工去执行 cpp、 cc1、 collect2 等命令,我们直接执行 gcc 并指定不同的参数就可以了。
九、很有用的选项
gcc -E hello.c // 查看预处理结果,比如头文件是哪个
gcc -E -dM hello.c > 1.txt // 把所有的宏展开,存在 1.txt 里
gcc -Wp,-MD,abc.dep -c -o hello.o hello.c // 生成依赖文件 abc.dep,后面 Makefile 会用
echo 'main(){}'| gcc -E -v - // 它会列出头文件目录、库目录(LIBRARY_PATH)
我的qq:2442391036,欢迎交流!