该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
9_2:静态库与动态库:动态库
场景:
丈量长度的仪器值数显示,在不同的国家显示不同的语言和单位,比如在中国可以用米meter,在英国可以用英尺feet;
假设有两种不同的仪器,一种测量比较大的物体,一种测量比较小的物体;分别叫machinesmall,machinebig;
我们为每种仪器编写自己的软件,使其可以显示测得的数据;
已有的模块9_1-hfcal库可以生成测量的数据值,用于显示;我们创建一个测试程序:
(Code9_1)
(文章末尾附上了目录结构)
9_1-hfcal.c
#include <stdio.h>
#include <9_1-hfcal.h>
void dispaly_length(float length){
printf("Length: %.2f meter.\n",length);
}
./9_1-include/9_1-hfcal.h
void dispaly_length(float length);
9_1-test.c
#include <stdio.h>
#include <9_1-hfcal.h>
int main() {
dispaly_length(115.24);
return 0;
}
编译、存档:
bogon:0911-1 huaqiang$ touch 9_1-hfcal.h
bogon:0911-1 huaqiang$ touch 9_1-hfcal.c
bogon:0911-1 huaqiang$ gcc -c 9_1-hfcal.c -I .
bogon:0911-1 huaqiang$ ar -rcs lib9_1-hfcal.a 9_1-hfcal.o
bogon:0911-1 huaqiang$ gcc 9_1-test.c -I . -L . -l9_1-hfcal -o 9_1 && ./9_1
log:
Length: 115.24 meter.
如果头文件位于9_1-include目录,目标文件放到9_1-libs目录下,编译、存档、链接可以这样写:
gcc -c 9_1-hfcal.c -I/Users/huaqiang/HQDSwiftDemo/C_Head_First/0911-1/9_1-include
ar -rcs /Users/huaqiang/HQDSwiftDemo/C_Head_First/0911-1/9_1-libs/lib9_1-hfcal.a 9_1-hfcal.o
gcc 9_1-test.c -I/Users/huaqiang/HQDSwiftDemo/C_Head_First/0911-1/9_1-include -L/Users/huaqiang/HQDSwiftDemo/C_Head_First/0911-1/9_1-libs -l9_1-hfcal -o 9_1
问题来了:
因为要在不同的国家显示,需要使用不同的语言和单位;
仪器的种类也有许多不同,比如50种;
如果为每一种机器都编写各种语言的程序,简直了!如果要修改程序岂不是要修改好多;
程序由碎片组成:
程序是由不同的目标代码组件而成的;
先创建.o文件和.a存档,然后再把它们链接成可执行程序;
一旦链接,就不能改变:
这种方式有个问题,就是程序是静态的;各个独立的目标文件链接成的可执行程序,没办法修改“原料”,除非重新构建整个程序;
在运行时动态链接:
之所以不能修改可执行文件中的目标代码,是因为它们在编译程序时静态链接在了一起;
那么,如果组成程序的多个文件,可以在运行前链接到一起,就可以避免这个问题;
可以把目标代码分别保存在单独的文件中,在程序运行时才把它们动态链接到一起;
.a能在运行时链接吗:
.o和.a文件本身就是独立的文件,使用它们在运行时动态链接是否可行呢?
不行,普通目标文件和存档包含的这点信息还不足以让它们在运行时链接,动态库文件还需要其他东西,例如要链接的文件名;
动态库——加强版目标文件:
动态库和我们创建的.o目标文件有点像,又有区别;
和存档也很像,也可以从多个.o目标文件创建;不同的是,这些目标文件在动态库中链接成了一段目标代码;
小结动态库:
有元信息的可重定位目标文件;
动态库由一个或多个.o文件创建;
动态库有一些额外信息,操作系统需要用这些信息把库链接到程序;
动态库的核心是一段目标代码;
如何创建自己的动态库:
(Code9_2)
9_2-hfcal.h
void dispaly_length(float length);
9_2-hfcal.c
#include <stdio.h>
#include <9_2-hfcal.h>
void dispaly_length(float length){
printf("Length: %.2f meter.\n",length);
}
9_2-test.c
/*
*
*/
#include <stdio.h>
#include <9_2-hfcal.h>
int main() {
dispaly_length(115.24);
return 0;
}
#include <stdio.h>
#include <9_2-hfcal.h>
void dispaly_length(float length){
printf("Length: %.2f feet.\n",length * 0.2);
}
头文件和源文件都在当前目录;
首先,创建目标文件:
gcc -I . -fPIC -c 9_2-hfcal.c
这次,我们在创建.o文件时多加了一个标志:-fPIC;
它的作用是告诉gcc你想创建位置无关代码;这样它们才能在运行时被决定加载到存储器的某个位置;
位置无关代码可以在存储器中搬来搬去;
说明:
事实上,大多数操作系统都不需要加这个选择;一些操作系统在加载动态库时会使用一种叫做存储器映射的技术,也就是说代码其实都是位置无关的,比如windows;
一种平台一个叫法:
大多数OS都支持动态库,工作方式也基本相同,但称呼却差很多;
windows——动态链接库,后缀.dll;
Linux和Unix——共享目标文件,后缀.so;
Mac——动态库,后缀.dylib;
创建方法:
我们新建一个目录9_2-libs用来存放库文件;
gcc -shared 9_2-hfcal.o -o ./9_2-libs/lib9_2-hfcal.dylib
注意:./9_2-libs的方式很好,'.'指代当前目录,这样就避免了写绝对路径的方式;
-shared:
告诉gcc把目标文件转化为动态库;
编译器在创建动态库时会把名字保存在文件中,一旦用某个名字编译了库,就不能再改了,这点很重要;
一些古老的Mac系统上,没有-shared选项,可以使用-dynamiclib代替;
编译程序:
创建了动态库就可以像静态库那样使用它;
gcc -I . 9_2-test.c -L ./9_2-libs -l9_2-hfcal -o 9_2 && ./9_2
log:
Length: 115.24 meter.
小结:
尽管使用的命令和静态库存档一模一样,但两者编译方式不同;
动态库在编译时,不会再可执行文件中包含库代码,而是插入一段用来查找库的“占位符”代码,并在运行时链接库;
Mac上指定的目标文件路径让程序知道,启动后可以去哪里找它;
Linux(多数Unix中)上稍不同,编译器只会记录libxxx.so库的文件名,不包含路径;
即,不把hfcal库保存到标准目录,程序就找不到它;
所以,Linux会检查保存在LD_LIBRARY_PATH变量中的附加目录;
只有把库目录加到该变量中,并export它,程序才能找到库;
环境变量的好处是方便使用不同版本的库,尤其是在开发新库时;
export命令:
Linux命令,用来将自定义变量设为环境变量;
如:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/libs
之后在执行程序;
当然,如果动态库已经在标准目录中,就不需要这样做了;
windows的话,和Linux类似,需要配置PATH环境变量;
这也是绝大多数动态库会保存到标准目录下的原因;
现在到了使用动态库的时候了:
我们针对英国的使用标准修改源文件,为了对比说明我们重新定义了源文件 9_2-hfcal_ch.c;
gcc -c -fPIC 9_2-hfcal_en.c -I .
gcc -shared 9_2-hfcal_en.o -o ./9_2-libs/lib9_2-hfcal_en.dylib
gcc -I . 9_2-test.c -L ./9_2-libs -l9_2-hfcal_en -o 9_2-en && ./9_2-en
log:
Length: 23.05 feet.
这里为了说明使用了新的源文件生成了新的动态库;
实际使用可以是:在英国使用的程序链接的动态库是我们修改之后的,这样不必修改代码就可以正确运行了;
如:
生成同名动态链接库,替换原有的;
gcc -shared 9_2-hfcal_en.o -o ./9_2-libs/lib9_2-hfcal.dylib
运行之前的目标程序;
./9_2
log:
Length: 23.05 feet.
和我们修改之后的9_2-en运行结果是一样的;
小结:
程序还是相同的程序9_2,只不过在运行时动态链接到了英国版的库;
程序不需要重新编译就能从新库中动态获取代码;
有了动态库,就能随时替换代码;
目录结构署名:
(P1)
要点:
-动态库在运行时链接程序;
-用一个或多个目标文件创建动态库;
-在一些机器上,需要使用-fPIC选项来编译目标文件;
- -fPIC令目标代码位置无关;有些机器上可以省略它;
- -shared编译选项可以动态创建动态库;
-动态库在不同机器上名字不同;之所以不同,是因为不同操作系统有不同的优化策略;
-如果把动态库保存在标准目录中,会更简单;否则,就需要设置PATH变量或LD_LIBRARY_PATH变量;
-编译器在编译动态库时会在文件中保存库名;重命名文件是无效的,需要重新编译它;
-使用静态库可以得到一个小而快的可执行程序,也方便copy复用;
-使用动态库允许在运行时配置程序;
C语言工具箱:
-#include<>会查找/usr/include在内的标准目录;
-ar命令会创建目标文件的存档;
- -l<路径名>会链接标准目录下的文件;
- -L<路径名>会在标准目录lib中添加目录;
- -I<路径名>在标准include目录列表中添加目录;
-gcc -shared把目标文件转化为动态库;
-运行库在运行时链接;
-库存档名形如libxxx.a;
-库存档是动态链接的;
-动态库的后缀名有.so、.dylib、.dll和.dll.a等;
-动态库在不同的操作系统上有不同的名字;