GCC编译C:C++的四个过程
前言
从源码到可执行程序,经历四个过程:预处理、编译、汇编和链接,前三步由使用编译器来完成、链接由链接来完成。
- 编译器将编译工作主要分为预处理,编译和汇编三部
- 连接器的工作是把各个独立的模块链接为可执行程序
- 静态链接在编译期完成,动态链接在运行期完成
阶段
阶段名 | 生成文件类型 |
---|---|
预处理(-E) | 生成 .i文件 |
编译(-S) | 生成 .s 文件 |
汇编(-c) | 生层 .o 文件 |
连接(-l) | 生成 可执行 文件 |
gcc 选项
选项 | 含义 |
---|---|
-v | 查看编译器的版本,像是 gcc 执行时的详细过程 |
-o <file> |
输出名为 file 的文件,这个不能与源文件名称相同 |
-E | 只预处理,不会编译、汇编、链接 |
-S | 只编译,不会汇编、链接 |
-c | 只编译和汇编,不会连接 |
在编译过程中,除非使用了-E、-S、-C选项(或者编译出错阻止了完整的编译过程),否则最后的步骤都是链接
文件类型
编译器支持许多和C语言程序相关的扩展名,对它们的说明如下:
扩展名 | 说明 | 后期操作 | 命令 |
---|---|---|---|
.c | C程序源代码,在编译之前要先进行预处理。 | 预处理、编译、汇编 | gcc -E test.c -o test.i |
.C | **C++**程序源代码,在编译之前要先进行预处理。 | 预处理、编译、汇编 | |
.cc | **C++**程序源代码,在编译之前要先进行预处理。 | 预处理、编译、汇编 | |
.cxx | **C++**程序源代码,在编译之前要先进行预处理。 | 预处理、编译、汇编 | |
.m | OC程序源代码,在编译之前要先进行预处理。 | 预处理、编译、汇编 | |
.i | C程序预处理输出,可以被编译。可以理解为预处理翻译(interpret)后的文件 | 编译、汇编 | gcc -s test.i -o test.s |
.ii | **C++**程序预处理输出,可以被编译。可以理解为预处理翻译(interpret)后的文件 | 编译、汇编 | |
.h | C程序头文件。(为了节省时间,许多源文件会包含相同的头文件,GCC 允许事先编译好头文件,称为“预编译头文件”,它合适情况下自动被用于编译。) | 通常不会出现在命令行 | |
.s | 汇编语言。 | 汇编 | gcc -c test.s -o test.o |
1 GCC编译C/C++的四个过程
gcc 是 GUN Compiler Collection的缩写。
预处理(pre-processing),E:插入头文件,替换宏,展开宏
gcc -E main.c -o main.i
编译(Compiling)S:编译成汇编
gcc -S main.i –o main.s
汇编(Assembling) c:编译成目标文件
gcc –c main.s –o main.o
链接 (Linking):链接到库中,变成可执行文件
gcc main.o –o main
./main
也可以一次性完成:
gcc main.c –o main
但一般情况下生成.o文件比较好,可以重定位文件,让别人使用
1.1 预处理
- 处理所有的注释,以空格代替
- 将所有的
#define
删除, 并且展开所有的宏定义 - 处理条件编译指令
#if
,#ifdef
,#elif
,#else
,#endif
处理条件编译指令 - 处理处理
#include
, 展开(evolve)被包含的文件 - 保留编译器需要使用的
#pragma
指令
C语言代码在交给编译器之前,会先由预处理器进行一些文本替换方面的操作,例如宏展开、文件包含、删除部分代码等。
在正常的情况下,GCC 不会保留预处理阶段的输出文件,也即 .i 文件。然而,可以利用 -E 选项保留预处理器的输出文件,以用于诊断代码。-E选项指示 GCC 在预处理完毕之后即可停止。
a、默认情况下,预处理器的输出会被导入到标准输出流(也就是控制台)
gcc -E main.c
b、可以利用-o选项把它导入到某个输出文件
gcc -E main.c -o main.i
或者
gcc -E main.c > main.i
表示把预处理的结果导出到 main.i
文件中。
c、使用-C
选项阻止预处理器删除源文件和头文件中的注释。
注意,这里是大写的 -C,不是小写的 -c。小写的 -c 表示只编译不链接。
gcc -E -C main.c -o main.i
对比之前的输出,可以发现,保留愿文件中的注释
1.2 编译阶段
- 对预处理文件进行一系列词法分析,语法分析和语义分析
- 词法分析主要分析关健字,标示符,立即数等是否合法
- 语法分析主要分析表达式是否遵循语法规则
- 语义分析在语法分析的基础上进一步分析表达式是否合法
- 分析结束后进行代码优化生成相应的汇编代码文件
编译器的核心任务是把C程序翻译成机器的汇编语言(assembly language)。
汇编语言是人类可以阅读的编程语言,也是相当接近实际机器码的语言。每种 CPU 架构都有不同的汇编语言。
实际上,GCC 是一个适合多种 CPU 架构的编译器,不会把C程序语句直接翻译成目标机器的汇编语言,而是在输入语言和输出汇编语言之间,利用一个中间语言,称为 RegisterTransfer Language(简称 RTL,寄存器传输语言)。借助于这个抽象层,在任何背景下,编译器可以选择最经济的方式对给定的操作编码。
通常情况下,GCC 把汇编语言输出存储到临时文件中,并且在汇编器执行完后立刻删除它们。但是可以使用-S选项,让编译程序在生成汇编语言输出之后立刻停止,而不删除临时文件。
-S可以理解为**Save
或者Stop
**,自己的理解仅供参考记忆。
如果没有指定输出文件名,那么采用**-S
**选项的 GCC 编译过程会为每个被编译的输入文件生成以.s作为后缀的汇编语言文件。如下例所示:
gcc -S ts.c
编译器预处理 ts.c
,将其翻译成汇编语言,并将结果存储在 ts.s
文件中,可以用文本编辑器察看.
指定输出文件名
gcc -S main.i -o filename.s
1.3 汇编阶段
- 汇编器将汇编代码转变为机器可以执行的指令
- 每个汇编语句几乎都对应一条机器指令
目标文件是一种中间文件或者临时文件,如果不设置该选项,gcc 一般不会保留目标文件,可执行文件生成完成后就自动删除了。
注意,使用-c选项表示只编译源文件,而不进行链接,因此,对于链接中的错误是无法发现的。
比如,调用一个不存在的函数,编译器会提示的错误。但是该命令不会输出错误信息。
gcc -c main.s -o main.o
**-c
选项:**只编译不链接,仅生成目标文件。
1.4 链接阶段
gcc main.o -o main.exe
连接器的主要作用是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接
静态链接
静态链接使用静态库进行链接,生成的程序包含程序运行所需要的全部库,可以直接运行,不过静态链接生成的程序比较大。
动态链接
动态链接使用动态链接库进行链接,生成的程序在执行的时候需要加载所需的动态库才能运行。
动态链接生成的程序体积较小,但是必须依赖所需的动态库,否则无法执行。
2 clang 分四步编译main.c
这里用的clang/clang++ 分四步编译main.c/main.cpp文件
2.1 预处理
clang++ -E main.cpp -o main.ii
2.2 编译阶段,生成汇编
clang++ -S main.ii -o main.s
2.3 汇编阶段,生成目标文件
clang++ -c main.s -o mian.o
2.4 连接阶段
clang++ mian.o -o main
2.5 执行
./main
2.6 源文件
#include <iostream>
int main() {
std::cout << "Hello Biter !" << std::endl;
return 0;
}
2.7 四部曲之一步到胃
clang++在编译的过程中,保存所有编译过程中产生的文件
2.8 产生中间文件
clang++ -save-temps main.cpp -o main
2.9 不保存中间文件
clang++ main.cpp -o main
3 案例
- 头文件
//
// Created by langkechanxin on 2021/1/7.
//
#define MAX(a,b) (((a)>(b)) ? (a) : (b))
int g_global = 10;
- 源文件
//
// Created by langkechanxin on 2021/1/7.
//
#include "test.h"
#define LOW 0
#define HIGH 255
int max(int a, int b)
{
return MAX(a,b);
}
int main()
{
int c = max(LOW, HIGH); // Call max to get the larger number
return 0;
}
预处理
gcc -E test.c -o test.i
编译
gcc -S test.i -o test.s
汇编
gcc -c test.s -o test.o
此时,借助 -c
选项,生成的中间产物 test.o
,此文件为二级制文件, 通过 ls -l
命令查看一下文件大小:
-rw-r--r-- 1 binny staff 912B Jan 7 15:41 test.o
借助二进制文件查看器,比如 UltraEdit 可以查看到地址范围:00000000h - 00000390(不包含)这是下一个字节的地址编号。一共 390hB=912B.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了