第五章 对连接器的思考
(1)目标文件并不能直接执行,它首先需要载入到链接器中。链接器确认main函数为初始进入点(程序开始执行的地方),把符号引用(symbolic reference)绑定到内存地址,把所有的目标文件集中在一起,再加上库文件,从而产生可执行文件。
(2)如果函数库的一份拷贝是可执行文件的物理组成部分,那么称之为静态链接;如果可执行文件只是包含了文件名,让载入器在运行时能够寻找程序所需要的函数库,那么称之为动态链接。
(3)动态链接的优点:
a.动态链接可执行文件比功能相同的静态链接可执行文件的体积小。节省磁盘空间和虚拟内存,因为函数库只有在需要时才被映射到进程中。
b.所有动态连接到某个特定函数库的可执行文件在运行时共享该函数库的一个单独拷贝。操作系统内核保证映射到内存中的函数库可以被所有使用它们的进程共享。如果可执行文件是静态链接的,则每个文件都将拥有一份函数库的拷贝,显然极为浪费。
c.动态链接似的函数库的版本升级极为容易。
d.动态链接允许用户在运行时选择需要执行的函数库。
(4)动态链接是一种“just-in-time(JIT)”链接,意味着程序在运行时必须能够找到它们所需要的函数库。链接器通过把库文件名或路径名植入可执行文件中来做到这一点,即函数库的路径名不能随意移动。如果把程序链接到/user/lib/libthread.so库,那么就不能把该函数库移动到其他的目录,除非在链接器中进行特别说明。
(5)动态链接库由链接编辑器ld创建。根据约定,动态库的文件扩展名为”.so”,表示”shared object(共享对象)”——每一个链接到该函数库的程序都共享它的同一份拷贝。而静态链接库则相反,每个对象都拥有一份该函数库内容的拷贝,显得浪费。动态链接库的最简单形式可以通过cc命令加上-G选项来创建,如下所示:
% cat tomato.c
my_lib_fun() { printf(“library routine called\n”); }
% cc –o libfruit.so –G tomato.c
然后,就可以利用这个动态链接库来编写程序了,并且使用下面这种方法与函数库进行链接:
% cat test.c
main() { my_lib_fun(); }
% cc test.c –L/home/linden –R/home/linden –lfruit
% a.out
library routine called
-L/home/linden和–R/home/linden选项分别告诉链接器在链接时和运行时从哪个目录寻找需要链接的函数库。
(6)函数库链接的5个特殊秘密
1.动态库文件的扩展名是”.so”,而静态库文件的扩展名是”.a”。(libname.so)
2.例如,通过-lthread选项,告诉编译链接到libthread.so。
3.编译器期望在确定的目录找到库。(一般在链接时使用-Lpathname和-Rphthname选项)。
4.观察头文件,确认所使用的函数库。(nm工具程序可列出函数库所包含的函数,nm工具用法详见百度百科)。
5.与提取动态库中的符号相比,静态库中的符号提取的方法限制更严。(函数库选项应置于何处:始终将-l函数库选项放在编译命令行的最右边)。
(7)Interpositioning(or “interposing”)是通过编写与库函数同名的函数来取代该库函数的行为。(应尽量避免Interpositioning行为)。
第六章 运动的诗章:运行时数据结构
(1)ELF:原意为“Extensible Linker Format”,可扩展链接器格式,现在代表“Executable and Linking Format”,可执行文件和链接格式。
COFF:Common Object-File Format,普通目标文件格式。
(2)size命令:当中一个可执行文件中运行size命令时,会显示这个文件中的三个段(文本段、数据段和bss段)的大小:
% echo; echo “text data bss total”; size a.out
text data bss total
1548 + 4236 + 4004 = 9788
检查可执行文件的内容的另一种方法是使用nm或dump实用工具。
(3)堆栈段的三个主要用途,其中两个根函数有关,另一个跟表达式计算有关:
1.堆栈为函数内部声明的具备变量提供存储空间。
2.进行函数调用时,堆栈存储与此有关的一些维护性信息。(过程活动记录,包括函数调用地址(即当所调用的函数结束后跳回的地方)、任何不适合装入寄存器的参数以及一些寄存器值的保存)。
3.堆栈也可以被用作暂时存储区。
(4)“悬垂指针(dangling pointer)”——它们并不引用有用的东西,而是悬在地址空间内。如果想要返回一个指向在函数内部定义的变量的指针时,要把那个变量声明为static。
(5)segjmp和longjmp
这两个函数协同工作:
1.setjmp(jmp_buf j)必须首先被调用。它表示“使用变量j记录现在的位置,函数返回零”。
2.longjmp(jmp_buf j, int i)接着被调用。它表示“回到j所记录的位置,让它看上去像是从原先的setjmp()函数返回一样,但是函数返回i,使代码能够知道它实际上是通过longjmp()返回的”。
3.当使用longjmp()时,j的内容被销毁。
setjmp保存了一份程序的计数器和当前的栈顶指针,longjmp恢复这些值,有效地转移控制并把状态重置回保存状态的时候。
与goto语句的区别:
goto语句不能跳出C语言当前的函数;用longjmp只能跳回到曾经到过的地方(longjmp更像是“从何处而来”,而不是“往哪里去”)。
例子:
#include <setjmp.h>
jmp_buf buf;
banana() {
printf(“in banana()\n”);
longjmp(buf, 1);
/*以下代码不会被执行*/
printf(“you’ll never see this, because I longjump’d”);
}
main() {
if(setjmp(buf))
printf(“back in main\n”);
else {
printf(“first time through\n”);
banana();
}
}
输出结果如下:
% a.out
first time through
in banana()
back in main
setjmp/longjmp最大的用途是错误恢复,但难以理解和调试,最好避免使用。