学习链接:
https://zhuanlan.zhihu.com/p/363987540
https://www.bilibili.com/read/cv14512363/#:~:text=C%2B%2B%E5%8F%AF%E4%BB%A5%E8%AF%B4%E6%98%AFC,%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E3%80%82
https://github.com/Light-City/CPlusPlusThings

【另:发现了一个有好看图的网站^.^ https://bobopic.com/

运行四步骤

1、预处理:把一些#define的宏定义完成文本替换,然后将#include的文件里的内容复制到.cpp文件里,如果.h文件里还有.h文件,就递归展开。在预处理这一步,代码注释直接被忽略,不会进入到后续的处理中,所以注释在程序中不会执行。
预处理之后的程序格式为*.i,仍是文本文件。

2、编译:把写的代码转为汇编代码,它的工作是检查词法和语法规则。
编译完成后会生成程序的汇编代码*.s,这也是文本文件。

3、汇编:汇编过程将上一步的汇编代码(*.s)转换成机器码(machine code)。
这一步产生的文件叫做目标文件(*.o),是二进制格式。

4、链接:其实就是一个“打包”的过程,它将所有二进制形式的目标文件(*.o)和系统组件(比如标准库、动态链接库等)组合成一个可执行文件。完成链接的过程也需要一个特殊的软件,叫做链接器(Linker)。
[注意:C++程序编译的时候其实只识别.cpp文件,每个cpp文件都会分别编译一次,生成一个.o文件。这个时候,链接器除了将目标文件和系统组件组合起来,还需要将编译器生成的多个.o或者.obj文件组合起来,生成最终的可执行文件(Executable file).]
(1)符号解析:将符号引用和符号定义建立关联后,将引用符号的地址重定位为相关联的符号定义的地址。程序中有定义和引用的符号(包括变量名,函数名),存放在符号表(.symtab)。符号表是一个结构数组,包含符号名、长度和位置等信息。编译器将符号的引用存放在重定位节(.rel.text和.rel.data)。链接器将每个符号的引用都与一个确定的符号定义建立关联。
(2)地址重定位:将多个代码段和数据段分别合并为一个单独的代码段和数据段,计算每个定义的符号在虚拟地址空间的绝对地址。将可执行文件中的符号引用处修改为重定位后的地址信息。


多线程

std::thread(线程入口函数[Callable Objects],入口函数的参数...)

std::thread可以用可调用类型构造,将带有函数调用符类型的实例传入std::thread类中,替换默认的构造函数。

启动了线程,你需要明确是要等待线程结束(join),还是让其自主运行(detach)。如果std::thread对象销毁之前还没有做出决定,程序就会终止(std::thread的析构函数会调用std::terminate()。

且需要对std::thread对象使用joinable(),当其返回true时,才能使用join()或detach()。

*可调用对象(Callable Objects)
   1 函数指针
   2 重载了operator()运算符的类对象,即仿函数
   3 lambda表达式(匿名函数)
   4 std::function

如果打算等待对应线程(join),则需要细心挑选调用的位置。当在线程运行之后产生异常,在join()调用之前抛出,就意味着这次调用会被跳过。

为避免应用被抛出的异常所终止,通常倾向于在无异常的情况下使用join()时,需要在异常处理过程中调用join(),从而避免生命周期的问题。还有一种方式是使用“资源获取即初始化方式”(RAII,Resource Acquisition Is Initialization),并且提供一个类,在析构函数中使用join()。