本次博文将会对编译过程中的预处理,编译,汇编,链接,做个个人的复习总结
准备代码
1,头文件
#pragma once
#include<iostream>
using namespace std;
void func(int a, char b);
void func(char b, int a);
2,函数文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"Func.h"
void func(int a, char b) {
cout << "func(int a,char b)" << endl;
}
void func(char b, int a) {
cout << "func(char b,int a)" << endl;
}
3,测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"Func.h"
int main() {
func(1, 'a');
func('x', 2);
}
在编译过程中,我们的第一步便是
一:预处理
过程通常包括:头文件展开 宏替换 条件编译 去掉注释……
头文件展开意味着将头文件的内容拷贝到Func.cpp里 和 test.cpp里的#include ” Func “中
注意:并不是把这些内容拷贝到cpp文件里,原因是,cpp文件是自己写的,编译器不能够擅自改变你的cpp
效果如图:
那么不把它拷贝到cpp文件中,它会变成什么呢?
答案是
而是生成新的文件Fun.i 和 test.i
Func.i
里有func()
函数的声明,也有func()
函数的定义
test.i
里有func()
函数的声明,但没有func()
函数的定义
生成了两个.i
文件后就开始编译
二:编译
编译的主要作用是检查代码,生成对应的汇编代码
由预处理生成的.i
文件随后被C或C++编译器编译,生成汇编代码(.s
文件)。编译器在这个阶段进行语法和语义分析,优化代码,然后生成对应的汇编指令。
Func.i 生成 Func.s,下面截取一部分Func.s的代码
test.i 生成 test.s,下面截取一部分test.s的代码
那么我们便会看到在汇编码中,两个func函数都有其相应的地址,这些地址怎么来的?
我们这里看到的地址,是因为它完全链接(第四步)成功后找到的,并不是我在编译的时候给的
那你竟然没有地址,为什么编译器又会让我们通过呢?
是因为,在函数声明有了 func(int ,char)
和 func(char, int)
,有了这两个声明,就相当于有了两个空头承诺,对编译器说 :没事,我最终会给你两个地址,你先继续编译下去
至于最后能不能通过,还得看以上两个承诺到最后能不能践行出来。
举个简单的例子吧
A国和B国原本是两个兄弟国家,后面B国听信谗言跟A国打起来了,但是B国远没有A国那么强大,就像C国借武器装备等,此刻空头承诺的两句声明(void func(int a, char b);
void func(char b, int a);)就相当于C国这么说
C国:没事,我有的是武器装备,你先打着,以后我会给你的。
B国:好嘞。
……
打着打着……
B国:哥们,武器呢?我快撑不住了
C国:抱歉,我的武器给别人了,不在我这了(即,没有Func函数中的函数)。
然后B国就炸了
这就是为什么,就算没有地址,程序也照样会先运行下去的原因
三,汇编
这一步要做的是
将汇编代码转换成二进制的机器码
Func.s→Func.o
test.s→test.o
.o文件是只有CPU才认识的,我们是不认识的
四,链接
将两个.o文件合并到一起,最后生成文件,至于什么文件,这就要看不同的OS了
-
可执行文件(Executable File):如果链接操作的目的是生成一个可以在操作系统上运行的程序,那么链接器会生成一个可执行文件。在不同的操作系统上,可执行文件的扩展名可能不同:
-
在Windows上,通常是
.exe
。 -
在类Unix系统(如Linux和macOS)上,通常是没有扩展名或者有时使用
.out
。
-
-
库文件(Library File):如果链接操作是为了生成一个库,供其他程序调用其中的函数或代码段,那么链接器会生成一个库文件。库文件的类型和扩展名也依赖于操作系统和库的类型:
-
静态库(Static Libraries):在Windows上是
.lib
,在类Unix系统上是.a
。 -
动态库(Dynamic Libraries):在Windows上是
.dll
,而在类Unix系统上是.so
(Shared Object)。
-
-
位置无关代码文件(Position Independent Code Files):某些编译器提供了生成位置无关代码(PIC)的选项,这种代码可以被加载到内存中的任意位置而不需要修改。这通常用于创建动态库。生成的文件可能是
.o
文件,但这些文件包含了可以被链接器在不同地址处合并的代码。 -
其他中间文件:在某些情况下,链接过程可能会生成其他中间文件,例如,符号表文件(Symbol Files),这些文件包含了程序中所有符号(如函数和变量)的列表,以及它们在内存中的位置。
回到我们之前讲到那两个承诺:现在是应该要那两个承诺实现的时候了,
如果两个承诺不是骗人的,则链接成功,如果两个承诺是骗人的,则链接失败,现在来证明一下老赖
讲完了四个步骤,我们借此机会思考一下C语言为什么不能重载
C语言重载与C++重载原因
C语言中有一个符号表的东西,里面存着函数的名字跟地址(只记录函数名跟地址映射),如果C语言可以重载,那么当我拿着重名的函数去找符号表,我该对应哪个地址呢 ,所以这就是C语言不存在重载的原因
而C++支持重载的原因是,它不同于C语言用函数名来找地址,而是它会根据声明的函数性质来用命名规则重新命名这个函数,以达到就算你是重名函数,只要你的性质不一样,就有不同的新命名,所以我就能通过这个新命名找到正确的地址,从而进行下一步。
下面用Linux的指令来证明
图片总结
以上便是我对代码编译过程的浅度理解,如果有大佬斧正我的文章错误,将不胜感激,谢谢各位大佬们的阅读。