- 前提:WSL2(Ubuntu)、gcc编译器。
- gcc安装命令:
-
sudo apt-get install gcc
-
- 查看gcc版本:
目录
1、编译过程
1.1、预处理
1.2、编译与汇编
1.3、链接
2、gcc实验
2.1、预处理
2.2、编译
2.3、汇编
2.4、链接
1、编译过程
- 对于c/c++编程,从源代码文件变成可执行文件,大致需要以下几步:
- 预处理 (Pre-Processing)
- 预处理器处理#include、#define等内容,把头文件复制到源文件中等。
- 编译 (Compiling)
- 得到以汇编语言写的,可读的文件。
- 汇编 (Assembling)
- 得到二进制格式,不可读的文件。
- 编译和汇编主要是分析源代码中的内容,然后转换为目标文件。
- 链接 (Linking)
- 静态库和动态库的处理体现在链接过程中,链接器把目标文件和库一起打包变成可执行文件。
- 【注】c/c++的编译过程和java、python等语言不同。
- 预处理 (Pre-Processing)
1.1、预处理
- 预处理主要的工作包括:
- 文件包含,通常包括系统的头文件和自定义的头文件。
- 宏定义的处理,例如,#define PI 3.14,替换相应的内容。
- 条件编译,例如,#ifdef、#ifndef、#else、#elif、#endif等,可以结合宏的使用实现部分编译,防止头文件重复包含等。
- 特殊控制,例如#pragma命令,#error命令等。
- 添加行号和文件名标识,清理注释内容等。
- 预处理指令不属于C/C++语言的标准语法,一定意义上也可以说预处理扩展了C/C++,因此预处理的具体语法和实现,与编译器以及平台相关。
1.2、编译与汇编
- 这是编译过程的核心部分,主要步骤有:
- 词法分析
- 语法分析
- 语义分析
- 代码优化
- 到这一步为止,我们从源文件 (和头文件) 得到了目标文件,通常每一个源文件都应该对应产生一个目标文件。下一阶段是链接,会把目标文件和库文件结合在一起,得到可执行文件 (二进制程序)。
- 为了得到正确的目标文件,这个环节重点检查:语法的正确,函数以及变量的声明和使用的正确。
- 【注】这一阶段,只要能在头文件中找到正确的声明即可,并不负责检查外部函数和变量是否被正确实现和定义,可以接收空头支票。
- 与之不同的是,下一阶段的链接器并不对源文件进行操作,它面对的就是目标文件和库文件,这个环节需要重点检查的是跨文件的符号 (外部函数和外部变量) 是否都被实现,并且把它们合并到一起 (它需要检查,编译时开出的空头支票是否都被兑现),如果出现了符号未定义或者符号重复定义,就会报链接错误。
1.3、链接
- 链接就是将目标文件和库文件打包、组装成可执行文件的过程。这里库文件就是早已完成的,可重复使用的成熟组件,而刚刚实现的源文件已经被处理为目标文件,等待着被一起打包组装。
- 链接环节要解决的问题是,怎么让各个不同的模块可以一起工作。有的文件中使用的符号在另一个文件中定义,有的函数具体在另一个文件中实现。链接需要让它们互相认识,重定位,确定符号的对应关系,统一进行地址分配等。
- 例如,hello.c源文件使用了printf这个函数,通过stdio.h这个头文件中的函数声明可以明白它的用法,但是它的具体实现其实是在动态库libc.so.6,链接的时候需要引用这个库文件。
- 根据指定的链接库函数的方式不同,链接过程可分为静态链接和动态链接两种。选择静态链接时,相当于把库中需要用到的部分直接拷贝过来;选择动态链接时,相当于仅仅登记一下库的基本信息,等执行的时候再去查找和加载动态库。
- 静态链接:从库中将需要用到的部分内容直接拷贝到最终的可执行程序中。
- 优点:可执行文件一旦生成,就与这个库没有联系,自己可以独立运行。
- 缺点:可执行文件体积较大,有很多重复的空间占用;如果更新升级库中的功能,则所有用到这个库的可执行文件必须重新编译。
- 动态链接:编译时暂时记录一下动态库的基本信息,但不会拷贝其中的具体内容,等到运行时再去寻找需要的动态库,将其加载到内存中一起执行。
- 优点:可执行文件体积较小,可以节约空间,多个程序可以共同利用同一个基础的动态库;可以灵活升级动态库的功能,如果保证二进制层面的兼容性,那么用到它的相关程序不需要重新编译。
- 缺点:可执行文件执行时仍然需要这个动态库,执行时很可能遇到动态库找不到,动态库版本不兼容等问题。
- 静态链接:从库中将需要用到的部分内容直接拷贝到最终的可执行程序中。
- 由于两种链接方式对应的文件格式不同,一个具体的库只能被动态链接或只能被静态链接。通常的库可以提供静态库版本和动态库版本,可以灵活选择。
- gcc如果同时找到了一个库的静态库版本和动态库版本,默认情况下会使用动态库,进行动态链接。
2、gcc实验
- 创建c文件:
-
mkdir project01 # 创建存放c文件的文件夹 cd project01 # 进入文件夹 vim hello.c # 创建并编写c文件
-
- 编写hello.c:
- 按 i,进入插入模式,编辑文件。
- 编辑完毕后按回退键,输入 :wq,保存文件。
2.1、预处理
- 对于gcc来说,它会调用一个叫cpp的预处理工具,全称为C Pre-Processor (C 预处理器),是一个与C编译器独立的小程序,注意不是C Plus Plus。
- gcc使用-E选项可以让编译过程在预处理步骤完成之后停止。
-
gcc -E hello.c -o hello.i
-
- 【注】必须指明输出到文件hello.i,否则会把预处理结果直接输出到终端,建议的文件后缀为 .i。
- 查看hello.i文件。
- 得到的hello.i文件内容很长,仍然是可读的。文件前面的绝大部分都是头文件中的,原本的hello.c的内容在最后几行。
2.2、编译
- 这是整个编译过程的核心步骤,gcc调用的处理程序一般是cc。
- gcc使用-S选项可以让编译过程在编译步骤完成之后停止。
-
gcc -S hello.c -o hello.s # 不加-o可以默认生成同名.s 汇编文件hello.s # 加-o选项则可以自定义输出文件名 gcc -S hello.i -o hello.s # 对.c或者.i文件都可以执行上述命令,效果一样
-
- 查看hello.s文件,即汇编语言文件。
2.3、汇编
- gcc在这一环节调用的工具通常是as。
- gcc使用-c选项可以让编译过程在编译步骤完成之后停止。
-
gcc -c hello.c -o hello.o # 使用-c选项会让gcc在第三步汇编之后就暂停 # 不使用-o选项会默认生成同名的.o目标文件 gcc -c hello.i -o hello.o gcc -c hello.s -o hello.o # 同样可以从.i .s中间文件出发,效果也是一样的
-
- 得到的是二进制格式的目标文件。
2.4、链接
- 链接过程是程序构建过程的最后一步,通常gcc调用ld来完成。
- 链接指的是把目标文件组合起来,变成可执行文件。由于当前例子只有单个文件,没有库文件,只会自动链接系统的一些库,暂时不需要关注这些细节。
-
gcc hello.o -o hello # 从目标文件得到可执行文件 gcc hello.o # 省略-o选项相当于 -o a.out gcc hello.c gcc hello.i gcc hello.s # 同样可以从.c .i .s文件出发,效果也一样
- 【注】gcc可以编译c++文件,但是不能自动链接c++程序使用的库,所以一般使用g++编译c++程序。
- 但是也可以使用gcc手动链接c++库,具体看:用gcc编译C++文件_gcc可以编译c++吗-CSDN博客
- 生成的hello为可执行文件,直接运行即可。
-
./hello
-