第一章:温故而知新
过度优化的问题:
我们知道volatile关键字可以阻止过度优化,因为它可以完成两件事:
- 阻止编译器为了提高速度将一个变量缓存到寄存器而不写回
- 阻止编译器调整操作volatile变量的指令顺序
然而,在优化这一块,不仅编译器会做优化,CPU也会做优化。volatile就管不着了CPU了。
经典的例子当然是单例模式。单例模式有一种常规的解决方案是DCL,也就是双重检查锁,但是在C++中new的步骤有是分为三个步骤:分配内存,调用构造函数,将内存地址用指针保存下来。CPU就要来搞怪,将第二步第三步乱个序。
一种解决方案是:调用CPU提供的barrier指令阻止将barrier指令之前的代码交换到barrier之后。但是这种方案不具有可移植性。部分实现代码如下:
if(!pInst){
lock();
if(!pInst){
T* temp = new T;
barrier();
pInst = temp;
}
unlock();
}
线程:
是程序执行流的最小单元。通常意义上,一个进程由多个线程组成,各个线程之间共享程序的内部空间(包括代码段,数据段,堆等)及一些进程级的资源(如打开文件和信号)
线程的访问权限:
线程调度与优先级
- 运行(Running):此时线程正在执行
- 就绪(Ready):此时线程可以立刻运行,但CPU已经被占用
- 等待(Waiting):此时线程正在等待某一件事件发生,无法执行
可抢占线程和不可抢占线程
线程在用尽时间片之后会被强制剥夺继续执行的权利,而进入就绪状态,这个过程叫做抢占,即之后执行的别的线程抢占了当前线程。
在早期的一些系统中,线程是不可抢占的。线程必须手动发出一个放弃执行的命令,才能让其他的线程得到执行。可以避免一些因为抢占式线程里调度时机不确定而产生的问题。非抢占式线程已经十分少见
写时复制(Copy on Write,COW)
指的是两个任务可以同时自由地读取内存,担任一一个任务试图对内存进行修改时,内存就会复制一份提供给修改方单独使用
线程安全
信号量:
一个初始值为N的信号量允许N个线程并发访问。线程访问资源的时候首先获取信号量,进行一下操作:
最后一条应该是错了,应该是信号量不小于1,唤醒一个等待中的线程
可重入(Reentrant)与线程安全
静态链接::第二章:编译与链接
被隐藏了的过程
通常将编译和链接合并到一起的过程称为构建(Build)
一个程序从代码到可以运行经过了四个步骤:预处理(Prepressing),编译(Compilation),汇编(Assembly),链接(Linking).c -> .i -> .s ->.o -> .exe
预编译
预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令。比如“#include”,“#define”等
编译
编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生产相应的汇编代码文件。
实际上 gcc 这个命令只是后台程序的包装,它会根据不同的参数要求调用预编译编译程序 cc1,汇编器 as,链接器 ld
词法分析:
首先源代码程序被输入到扫描器,进行简单的词法分析,运用一种类似于有限状态机的算法将源代码的字符序列分割成一系列的记号(一般有关键字,标识符,字面量,特殊符号)
语法分析:
语法分析器将对由扫描器产生的记号进行语法分析,产生语法树,采用了上下文无关语法的分析手段。
语义分析:
编译器所能分析的语义是静态语义,所谓静态语义是指在编译器可以确定的语义,与之对应的动态语义就是只有在运行期才能确定的语义
主要检查声明和定义,类型转换是否出错等。做完这一步之后,语法树的每个节点都会有对应的类型。这一步还会对符号表里的符号进行更新。
中间语言生成
图中(2+6)被优化成 8
目标代码生成与优化
主要是生成汇编代码,以及对汇编代码的优化。比如说,在优化这个阶段,可能导致指令重排序,这就引发了单例的一些问题。
汇编
汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。
到这一步为止,输出的是目标文件(Object File)