其它书籍笔记

  • STL源码剖析
  • 大话数据结构
  • 程序员的自我修养链接加载和库

下面是阅读的一些其它书籍时做的一些笔记,主要是一些自己记忆的不是很准确的东西或者最开始不是很明白的东西,明白的就没有记录记录下来了。

《STL源码剖析》

下面是《STL源码剖析》这本书的一些笔记,这本书需要记忆的东西不是很多,主要是一些理解性的东西,看看源码中的一些编程习惯和方式收获还是蛮大的,而且侯捷大师的书写的是真的很不错,看了收获很大。

所谓仿函数,就是使用起来像函数一样的东西,如果你针对某个class进行operator()重载,它就成为一个仿函数,至于要成为一个可配接的仿函数,它还需要一些额外的努力。

迭代器是一种行为类似于指针的对象。

在C++中,函数如果要返回左值,都是以by reference

迭代器相应型别:
1. value type
2. different type
3. reference type
4. point type
5. iterator_category

其它书籍笔记_第1张图片

vector是一种单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。所谓双向开口,意思可以在头尾两端分别做元素的插入和删除操作。

dequevector的最大差异,一是在于deque允许常数时间内对头端元素进行插入或移除操作;二是在于deque没有所谓的容量capacity概念,因为它是动态以分段连续空间组合而成的,随时可以增加一段新的空间并链接起来。

list不仅是一个双向链表,而且还是一个环状双向链表。

其它书籍笔记_第2张图片

《大话数据结构》

下面是《大话数据结构》这本书的一些笔记,主要是图的一些基本的概念,基本的一些数据结构在leetcode上基本上都刷过了,但是图的可能比较麻烦一些,很多一些概念首先要明白了才能研究这种数据结构。

RB-tree不仅是一个二叉搜索树,而且必须满足以下规则:
1. 每个节点不是红色就是黑色
2. 根节点为黑色
3. 如果节点为红,其子节点必须为黑
4. 任一节点至NULL(树尾端)的任何路径,所含之黑节点数必须相同

无向边:若顶点 vi vj 之间的边没有方向,则称这条边为无向边。用无序偶对 (vi,vj) 表示,如果图中任意两个顶点之间的边都是无向边,则称该图为无向图

有向边:若从顶点 vi vj 的边有方向,则称这条边为有向边,也称为弧,如果图中任意两点顶点之间的边都是有向边,则称该图是有向图

在图中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图

在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图

在有向图中,如果任意两个顶点都存在方向互为相反的两条弧,则称该图为有向完全图

有很少条边或弧的图称为稀疏图,反之称为稠密图

有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数字叫做,带权的图通常叫做

图中顶点间存在路径,两顶点存在路径说明是连通的,如果路径最终回到起始点则称为环,当中不重复叫简单路径,若任意两顶点都是连通的,则图就是连通图,有向则称强连通图,图中有子图,若子图极大连通则就是连通分量,有向的则称强连通分量

无向图中连通且n个顶点n-1条边叫做生成树,有向图中一顶点入度为0其余顶点入度为1的叫做有向树,一个有向图由若干棵有向树构成生成树森林

数据结构的最终目的是提高数据的处理速度,索引就是为了加快查找速度而设计的一种数据结构。索引就是把一个关键字与它对应的记录相关联的过程。一个索引由若干个索引项组成,每个索引项至少应包含关键字和其对应的记录在存储器中的位置。

索引按照结构可以分成线性索引树形索引,和多级索引

  • 线性索引就是将索引项集合组织为线性结构,也称为索引表。包括稠密索引分块索引倒排索引
  • 对于稠密索引这个索引表,索引项一定是按照关建码有序的排序。
  • 分块索引:对数据集进行分块,是其分块有序,然后再对每一块建立一个索引项,从而减少索引项的个数。分块有序是把数据集的记录分成若干块,并且这些块需要满足两个条件:块内无序,块间有序。
  • 倒排索引:由属性来确定记录的位置。

《程序员的自我修养–链接,加载和库》

下面是《程序员的自我修养–链接,加载和库》的部分笔记,重点看了下静态链接和动态链接,因为这个问题太多考官问了,而这本书感觉是讲的最清晰的。其它部分也看了一遍,但是没有做笔记,发现忘得都差不多了,囧。。。。

加载程序将符号的虚拟地址写入到可执行文件模块的导入段,这使的在程序引用导入的符号时,实际引用的是正确的内存地址。

一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成,通常意义上,一个进程由一个到多个线程组成,各个线程之间共享程序的内存空间(包括代码段,数据段,堆等)及一些进程级的资源(如打开的文件,信号等)。

可重入与线程安全
一个函数被重入,表示这个函数没有被执行完成,由于外部因素或内部调用,又一次进入该函数执行,一个函数要被重入,只有两种情况:
1. 多个线程同时执行这个函数
2. 函数自身调用自身

一个函数被称为可重入的,表明该函数被重入之后不会产生任何不良后果。

一个函数要成为可重入的,必须具有如下特点:
1. 不使用任何(局部)静态或全局非const变量
2. 不返回任何(局部)静态或全局非const变量的指针
3. 仅依赖于调用方提供的参数
4. 不依赖任何单个资源的锁
5. 不调用任何不可重入的函数

volatile关键字防止编译器过度优化:
1. 阻止编译器为了提高速度将一个变量缓存到寄存器而不写回
2. 防止编译器调整操作volatile变量的指令顺序


gcc -E 预处理  
gcc -S 生成汇编文件  
gcc -c 编译生成.o文件  

模块之间如何组合的问题可以归结为模块之间如何进行通信的问题,最常见的C/C++模块之间的通信方式有两种:一种是模块之间的函数调用 ,另一种是模块之间的变量访问 。函数调用须知道目标函数的地址,变量访问也需要知道目标变量的地址,所以这两种方式都归结为一种方式,那就是模块间符号的引用。模块间依靠符号 来通信。

组装模块的过程就是链接,链接的主要内容就是把各个模块之间的相互引用的部分处理好,使得各个模块之间能够正确地链接。

链接过程主要包括地址与空间分配符号决议与重定位

地址修正的过程叫做重定位。每个要被修正的地方叫做一个重定位入口,重定位所做的就是给程序中每个这样的绝对地址引用的位置“打补丁”。使它们指向正确的地址。

目标文件从结构上来讲,它是已编译后的可执行文件格式,只是还没有进过链接的过程,其中可能有些符号或有些地址还没有被调整,其实它本身就是按照可执行文件格式存储的,只是跟真正的可执行文件在结构上稍有不同。


ELF Header  
.text  
.data  
.bss  
...  
Section header table  段表  
string tables  字符串表  
symbol tables  符号表  


段表就是保持段基本属性的数据结构,段表是ELF文件中除了文件头以外最重要的数据结构,它描述了ELF的各个段的信息,比如每个段的短名,段的长度,在文件中的偏移,读写权限及段的其它属性。也就是说,ELF文件的段结构就是由段表来决定的,编译器,链接器和装载器都是依靠段表来定位和访问各个段的属性的。

一个重定位表同时是ELF的一个段,链接器在处理目标文件时,须要对目标文件中的某些部位进行重定位。即代码段和数据段中哪些对绝对地址引用的位置,这些重定位的信息都记录在ELF文件的重定位表里。

在链接中,我们将函数和变量统称为符号,函数名或变量名就是符号表

链接器一般都是采用两步链接 的方法,也就是说整个链接过程分成两步:
1. 第一步:空间与地址分配 ,扫描所有的输入文件,获得它们的各个段的长度,属性和位置,并且将输入目标文件中的符号表中的所有符号定义和符号引用搜集起来,统一放到一个全局符号表中,这一步中,链接器能够获得所有输入目标文件的段长度,并且将它们合并,计算输出文件中各个段合并后的长度和位置,并建立映射关系。
2. 第二步:符号解析与重定位 ,使用上面第一步收集到的所有信息读取输入文件段的数据,重定位信息,并且进行符号解析与重定位,调整代码中的地址等。

符号地址 的确定:
当前一步完成后,链接器开始计算各个符号的虚拟地址,因为各个符号在段内的相对地址是固定的,只不过链接器必须要为每个符号加上一个偏移量,使得他们能够调整到正确的虚拟地址。

在完成空间和地址的分配步骤以后,链接器就进入了符号解析与重定位的步骤,这也是静态连接的核心内容。

每一个要被重定位的ELF段都有一个对应的重定位表,而一个重定位表往往就是ELF文件中的一个段,所以其实重定位表也可以叫做重定位段

重定位 过程也伴随着符号的解析过程,每个目标文件都可以定义一些符号,也可能引用到定义在其它目标文件的符号。重定位的过程,每个重定位的入口都是对一个符号的引用。那么当链接器要对某个符号的引用进行重定位时,它就要确定这个符号的目标地址,这时候链接器就会去查找由所有输入目标文件的符号表组成的全局符号表。找到相应的符号后进行重定位。

一个静态库可以简单地看成一组目标文件的集合。

装载方式
1. 覆盖装入
2. 页映射

进程虚拟地址空间的概念:操作系统通过给进程空间划分出一个个VMA(virtual memory area)来管理进程的虚拟地址空间,基本原则是将相同权限属性的有相同映射文件的映射成一个VMA。

装载一个PE可执行文件是比ELF文件相对简单的过程:
1. 先读取文件的第一页,在这个页中,包含Dos头,PE文件头和段表
2. 检查进程地址空间中,目标地址是否可用,如果不可用,则另选一个装载地址。这个问题对于可执行文件来说基本不存在,因为它往往是进程第一个装入的模块,所以目标地址不可能被占用,主要针对DLL文件的装载而言
3. 使用段表中提供的信息将PE文件中所有的段一一映射到地址空间中的相应位置
4. 如果装载地址不是目标地址,则进行Rebasing
5. 装载所有PE文件所需要的DLL文件
6. 对PE文件中的所有导入符号进行解析
7. 根据PE头制定的参数,建立初始化栈和堆
8. 建立主线程并且启动进程

Rebasing:基址重置

要解决空间浪费和更新困难这个问题最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不再将它们静态地链接在一起,简单地将,就是不对那些组成程序的目标文件进行链接,等到程序要进行运行时才进行链接,也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接

动态链接的基本思想是把进程按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有的程序模块都链接成一个单独的可执行文件。

共享对象在编译时不能假设自己在进程虚拟地址空间中的位置,与此不同的是,可执行文件基本可以确定自己在进程虚拟地址空间中的起始位置,因为可执行文件往往是第一个被加载的文件,它可以选择一个固定的空闲地址。

希望程序模块中共享指令部分在装载时不需要因为装载地址的改变而改变,所以实现的基本思想就是把指令中那些需要修改的部分分离开来,跟数据部门分开放在一起,这样指令部门就可以保持不变,而数据部分可以在每个进程中拥有一个副本,这就是地址无关代码的技术。

基址重置 = 装载时重定位

动态链接ELF 中最重要的结构应该是“.dynamic”段,这里面保存了动态链接器所需要的基本信息,比如依赖于那些共享对象,动态链接符号表的位置,动态链接重定位表的位置,共享对象初始化代码的地址等。

目标文件的重定位是在静态链接时完成的,共享对象的重定位是在装载时完成的。

动态链接 基本分成3步:
1. 显示启动动态链接器本身
2. 装载所需要的共享对象
3. 重定位和初始化

你可能感兴趣的:(其它书籍笔记)