CSAPP第七章
CSAPP第七章
7.1
1.函数后没跟函数体则为声明,跟了则为定义
下列情况仅为声明
1: 仅仅提供函数原型:void display();
2: extern int a;
3: class A;
4: typedef 声明;
5: 在类中定义的静态数据成员的声明
class A{
public:
static int a; //声明
};
局部变量 仅由模块m定义和引用的本地符号
e.g.在模块中定义的带static的C函数和全局变量
注意函数中的局部变量如temp存在栈中,不存在symtab中
2.不在此处定义,要声明全局变量时,要加extern关键字
7.4
1..data 已初始化的全局和静态变量
.bss未初始化的静态变量和已被初始化为0的全局或静态变量
在目标文件中不占据实际空间
COMMON未初始化的全局变量
.text已编译程序的机器代码
2.判断某个文件中的符号是否是该模块的.symtabl节中有的符号表条目的方法
如果有,还需要判断其符号类型,所处在的节,还有定义的模块
首先先看这个符号在文件中是否是某个函数里面的非静态局部变量,如果是,那么这个符号就不在这个模块的.symtabl条目
接下来判断符号类型,
如果是extern的全局变量,那么他是外部的(extern)。
如果是非静态的函数名,那么它是全局的(global)。
如果是static的静态变量,那么它是本地的(local)。
如果是static的静态函数名,那么它是本地的(local)。
其他的正常的变量都是全局的(global)
接着判断定义模块
根据符号类型就可以判断了,
外部(extern)的的就不是本模块定义的
剩下的本地的(local)和全局的(global)都是本模块定义的
最后判断它所在模块中所处的节
如果它是函数名,就在.text节中
如果它是初始化过的变量,就在.data节中
如果它是未初始化的变量,就在.bss节中
特例
(如果static int count=0这个count不管是全局还是局部,在ubuntu里面测试了之后发现它都在.bss段中)
————————————————
转自 原文链接:https://blog.csdn.net/qq6304403348/article/details/73056532
练习题7.3
.o目标文件
.a静态库(存档文件)
链接器维护一个可重定位目标文件的集合E(这个集合中的文件就是确定的最后合并成可执行文件的模块文件),
一个未解析的符合(即引用了但尚未定义的符号)集合U,以及一个已解析(前面已经定义但没有被引用)集合D。
对于命令行上的每个文件 f ,链接器会判断 f 是一个目标文件还是存档文件。
1.判断输入文件类型
如果是目标文件
链接器将会把这个文件添加到集合E,并根据符号引用情况修改集合U和D的状态。然后处理下一个文件。
如果是存档文件
链接器将尝试匹配集合U中未解析的符号和存档文件成员定义的符号,如果存档文件的成员m定义了一个符号来解析U中的一个引用,
那么久将m加入到集合E中,然后修改U和D的状态。对存档文件中的每个成员都重复这个过程,直到U和D不再发生变化,然后简单地丢弃
不包含在集合E中的成员目标文件。然后链接器继续处理下一个文件。
2.判断集合U是否为空
如果链接器扫描完命令行上的所有文件后,集合U仍不为空,则说明引用了未定义的符号,则链接器将会报错并终止程序。
如果链接器扫描完命令行上的所有文件后,集合U仍为空,则将合并和重定位E中的目标文件,并输出可执行文件。
注意命令行上的库和目标文件的顺序
一般将库放在命令行的结尾
1.如果库之间是相互独立的,则可以以任意的顺序放在命令行的结尾处。
2.如果库之间是相互依赖的关系,则必须对他们排序,使得对于每个被存档文件的成员外部引用的符号s,在命令行中至少有一个s的定义是在对s的引用之后的。
例如,a和b表示当前目录中的目标模块或者静态库,而a->b表示a依赖于b,也就是说b定义了一个被a引用的符号。
p.o->libx.a->liby.a且liby.a->libx.a->p.o
可得最小命令行 gcc p.o libx.a liby.a libx.a
注意不要写成 gcc p.o libx.a liby.a libx.a p.o //要注意区分存档文件和目标文件,目标文件将整个添加到E,并不会像存档文件一样遍历。
if (r.type == R_386_PC32) {
refaddr = ADDR(s) + r.offset;
*refptr = (unsigned) (ADDR(r.symbol) + r.addend - refaddr);
}
if (r.type = R_386_32)
*refptr = (unsigned)(ADDR(r.symbol) + r.addend);
重定位 PC相对引用
4004de :e8 05 00 00 00 callq 4004e8<sum>
前两位e8为call指令操作码 后面的位为重定位引用的值
结合书第480页
在运行时,call指令将存放在地址0x4004de
当CPU执行call指令时,PC的值为0x4004e3(紧随call指令的地址)
CPU执行步骤1)将PC压入栈
2)PC<-PC+0x5=0x4004e3+0x5=0x4004e8
重定位引用的值=重定位到的地址+r.addend-重定位引用的运行时地址
重定位引用的运行时地址=.text重定位到的地址+r.offset
重定位引用的十六进制地址为addr(r.symbol)
重定位PC绝对引用
*refptr = (unsigned)(ADDR(r.symbol) + r.addend)
动态链接与静态链接
静态链接 载入代码就会把程序要用到的动态代码载入
动态链接 并不在一开始完成动态链接,而是在真正调用动态库代码时载入程序才计算要调用部分的逻辑地址 载入所需要的动态代码
动态库:
- 类库的名字一般是 libxxx.so
- 共享:多个应用程序可以使用同一个动态库,启动多个应用程序的时候,只需要将动态库加载到内存一次即可;
静态库
类库的名字一般为libxxx.a
优点:
代码装载速度快,执行速度略比动态链接库快;
缺点:
1、浪费内存和磁盘空间、模块更新困难;
2、会给对程序的更新、部署和发布带来很多麻烦。
例如:在多进程操作系统情况下,假如每个程序内部除了都保留着printf()函数、scanf()函数、strlen()等公用库函数,还有数量相当可观的其他库函数及它们所需要的辅助数据结构。
假如说程序program1和program2都公用lib.o这个模块,同时运行Program1和program2时,lib.o在磁盘中和内存中都有两个副本。
动态链接基本思想:把链接过程推迟到运行时再进行。
优点:
1、解决了共享目标文件多个副本浪费磁盘和内存空间的问题;
2、在内存中共享一个模块:节省内存,还可减少物理页面的换入换出,也可增加CPU缓存的命中率,因为不同进程间的数据和指令访问都集中在了同一个共享模块上。
3、加强程序的兼容性,一个程序在不同平台运行时可以动态地链接到由操作系统提供的动态链接库。
例如:program1编译后生成program1.o目标文件,其运行后,当再运行program2,只需加载program2.o,不需重新加载lib.o,因为内存中已经存在来一份lib.o的副本。
缺点:如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,则会出现错误。