编译过程参考:
七、C程序编译过程、预编译、链接
八、可执行文件结构


一、了解编译器集合

编译的四个过程,必须用到的基本程序 cpp、cc1、as、collect2/ld、gcc

gcc编译时4个过程自动完成,也就是自动调用 cpp、cc1、as、collect2/ld4个程序自动执行4个编译步骤的,
我们既然已经知道了这4个过程,那么我们就自己一步一步实现这4个过程,然后得到最终的可执行文件

为了方便我们查看预编译后的结果,我们先在.c中加入宏、条件编译、头文件包含

#include <stdio.h>
#include <stdlib.h>

#define NUM 100;

int main()
{
    int a = NUM;
    #if 1
    printf("dddddddddddddddddddd\n");
    #endif

    printf("Hello World!\n");
    return 0;
}

1. cpp

路径:\mingw64\bin\

演示

$ cpp main.c -o main.i

o 选项用于指定目标文件,表示将预处理后的结果保存到.i文件中

我们可以打开main.i文件,验证预处理后.c中的宏、include、条件编译,在.i中还能否见到

可以发现,main.i 文件中头部include的文件的内容都拷贝进来了,
int a = 100; NUM 直接替换为100

C 语言的代码首先由预处理器(preprocessor)对 #include(文件包含)、#define (宏定义)
与条件编译(#ifdef #ifndef #if #undef)进行处理

具体来说:

  1. 将被include文件中的内容复制到源文件中;
  2. 然后将所有宏进行替代(根据宏的定义,替换掉所有的宏,例如定义了#define true 1
    那么会将所有语句中的true1进行代换,预处理后的文件中不再含有任何宏定义)
  3. 处理条件编译的语句(只保留符合编译条件的语句,不符合条件的语句会删除掉);

预处理之后的文件不会出现以#开头的任何语句,这就是预处理

2. cc1

路径:\mingw64\libexec\gcc\x86_64-w64-mingw32\8.1.0

编译程序(编译器):将C源码翻译为汇编源码

演示

$ cc1 main.i -o main.s

其实 cc1 本身也包含 cpp 处理的功能,也就是说可以直接使用cc1.c——>.s
cc1会完成之前的预处理的功能。

$ cc1 main.c -o main.s

不过以上命令并不能被成功执行,因为还缺参数,他会提示找不到头文件,
至于缺什么参数,我们这里就不关心了

3. as

路径:\mingw64\bin\

汇编程序(汇编器):将汇编源码翻译为纯二进制的机器指令,放到 .o 文件中

演示

$ as main.s -o main.o

4. ld、collect2

ld 路径:\mingw64\bin\\mingw64\x86_64-w64-mingw32\bin
collect2 路径:\mingw64\libexec\gcc\x86_64-w64-mingw32\8.1.0

(a)链接程序(链接器(静态链接器))

  • 将所有的.o文件(自己的、编译器提供的)和库(动态库、静态库)链接在一起,得到可以运行的可执行文件。

(b)collect2ld 之间的关系

  • collect2是对ld进一步封装得到的,这两个都可以用于链接

(c)演示

  • 实际上我们完全可以自己调用collect2ld这两个程序(命令)来进行链接,
    但是链接并不是一件容易的事情,链接的时候需要跟大量的参数和选项,
    这些参数和选项我们自己并不清楚,所以我们自己调用collect2ld来链接的话,实际上操作起来比较困难
  • 所以链接的话,我们直接使用gcc程序来链接,gcc会自动调用collect2或者ld来链接,
    并且自动指定需要的各种的选项和参数,我们并不是需要关心
gcc main.o -o main.exe

或者

gcc main.o  #如果不指定可执行文件名字的话,默认为a.exe

二、gcc/g++/c++

其实 gcc/mingWgcc/g++ 这几个都能编译C程序

1. gcc/g++/c++关系

c++/g++是用来编译c++程序的,但是由于c++程序兼容c,所以c++/g++也能编译c程序

  1. gccg++都可以编译c代码与c++代码
    但是:
    后缀为.c的,gcc把它当做C程序,而g++当做是C++程序
    后缀为.cpp的,两者都会认为是C++程序
  2. 编译阶段,g++会调用gcc,对于c++代码,两者是等价的,
    但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接
  3. 编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++
    因为gcc命令不能自动和C++程序使用的库联接(当然可以选择手动链接,使用命令如下),
    gcc main.cpp -lstdc++
    
    所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价

2. gcc/g++/c++程序的作用

gcc/g++/c++其实是总的调度程序,它按照需求去调用cpp/cc1/as/collect2/ld等程序,完成对应四个过程

为什么要一个总的调度程序?

通过前面的讲解知道,虽然我们能够自己调用cpp/cc1/as/collect2/ld来完成四个过程,得到最后的可执行文件,但是如果我们都手动去执行这4个过程,就非常麻烦

有了gcc后,可以调用gcc一次性快速完成四个过程,gcc会自动调用cpp/cc1/as/collect2/ld来完成

一次性完成时,中间产生的.i/.s/.o都是临时文件,编译后会被自动删除,这些文件我们并不关心

使用gcc这个总调度程序,一次性完成所有过程时,编译速度非常快,用起来非常方便