C++是如何工作的
头文件
带有#的为preprocessor statement,即预处理指令 该类指令发生在真正的编译之前 当编译器收到一个源文件时,做的第一件事情就是预处理所有预处理指令
#include <iostream>即找到iostream文件,将该文件中的所有内容复制粘贴到目前的文件里 这些被 include 的文件一般被称为header file,即头文件
每个 cpp 程序都有一个类似main函数的东西,被称作entry point 后面了解到这个必须的原因在于.exe 可执行文件必须需要一个入口,可在设置中自定义入口点(不一定必须是 main function)
只有主函数可以不返回任何值,默认返回 0
preprocessor statement被评估后,我们的文件会被编译,这个阶段,编译器把我们的 cpp 代码转化为实际的机器码
编译器
所有.cpp 文件都会被编译,.h 头文件不会被编译,它会通过 include 到 cpp 中,然后被编译 每个.cpp 文件都被编译成一个Object文件(.obj)
链接器
将所有.obj 文件连在一起,成为一个.exe 文件
如果在项目文件中调用其他文件的函数,需要在当前文件中声明该函数,告诉编译器这个函数存在,至于这个函数在哪,怎么用,由链接器实现.
C++的编译器是如何工作的
抽象语法树
编译器的工作是把代码转化为constant data(常量数据)和instructions(指令),构建抽象语法树后开始生成机器代码。
cpp 中没有文件的概念,文件只是给编译器提供源码的一个方式
.cpp 告诉编译器用 c++编译->编译器当成一个translation unit->得到一个.obj
预处理(preprocessing)
处理所有以
#开头的指令,进行纯文本替换
常见预处理命令
#include: 指定包含的文件,预处理器会打开该文件,然后把里面的内容完整复制粘贴到当前代码文件中.#define:#define INTEGER int在代码中查找INTEGER,将其换成int#if和#endif: 判断if后面的条件,如果不为真就把if到endif的内容删去(不出现在.i文件中)
这一步完成后,会生成一个预处理后的中间文件(通常后缀是 .i)
编译(Compilation)
编译器将.i文件转为汇编语言的.asm(windows)/.s(Unix/linux)
汇编器将.asm(windows)/.s(Unix/linux)文件转化为.obj文件
C++的链接器是如何工作的
一旦编译了文件,我们就需要一个叫Linking(链接)的过程,其主要工作是找到每个符号和函数的位置,并将他们链接在一起。由于编译时每个.cpp 都会变成.obj,我们如果要做一个很多.cpp 文件的项目时,每个文件实际没法沟通,因此需要Linker链接器。
就算你都写在一个文件里了,找到程序的entry point,也就是主函数在哪儿,也需要链接器来链接主函数和其他东西
如果一个函数在当前文件里声明了且没在其他文件中定义,只要不使用它,链接就不会发现错误.
但如果有个函数在当前文件中定义,同时在函数体中使用了声明了但不存在的函数(无法link到),尽管不使用这个新定义的函数,链接也会报错,因为不能保证其他文件没使用该函数.——这种情况可以使用static来声明新的函数,保证这个函数只在这个translation unit中有效
如果是在编译期间报错,报错代码 Error是以 C开头。 如果是在链接期间报错,报错代码 Error是以 LNK开头
报错
Unresolved external symbol :另一文件的函数定义和当前文件函数声明有区别,若没有调用则不会报错。或者可通过
static解决,可使链接只发生在文件内部。
Duplicate symbol: 有函数或者变量名字和签名相同,两个函数名字、返回值、参数相同,Linker 不知道链接哪个。很可能发生,比如.h 中调用,又
#include到了别的文件中,就会造成此问题。
可通过static解决,使链接只发生在文件内部。原理是#include的是static的函数,只在当前translation unit中有效
或者inline。由于#include是直接将原函数copy过来,而将函数声明为inline后,这个函数被调用后,只会把函数体内的代码替换到调用的地方,而不复制声明.
推荐的方法是将函数声明都放在.h 头文件中,定义放在.cpp 中