Linux 静态与动态库创建及使用实例


上为GCC 编译过程示意图
gcc  -E  文件名.c  -o  *.i --> *.i <-- 用i做扩展名是因为规定*.i文件为已经预处理过的C源代码
预处理后的 C 文件,预处理后即停止,不进行编译,预处理后的代码送往标准输出,所以要用-o来输出成文件;

gcc  -S  文件名.c --> *.s
编译后即停止,不进行汇编,对于每个输入的非汇编语言文件,输出文件是汇编语言文件;
如果没有使用`-o'选项,默认的输出结果是:文件名.s <-- 汇编文件

gcc  -c  文件名.c --> *.o
编译或汇编源文件,但是不作连接,编译器输出对应于源文件的目标文件;
如果没有使用`-o'选项,默认的输出结果是: 文件名.o <-- 目标文件

注:以上三个选项不能组合使用,因为它们各是在产生一个新可执行程序的一个阶段后停止输出。

gcc  文件名.c  -o  文件名--> 文件名
指定输出文件为指定文件名,该选项不在乎 gcc 产生什么输出
(可执行文件,目标文件,汇编文件还是预处理后的 C 代码)
如果没有使用`-o'选项,默认的输出结果是:可执行文件为 a.out
如果gcc后没有以上三个选项之一,就会完成:预处理,编译,汇编,连结,四个阶段产生新可执行程序。

基本语法结构:(由以下四部分组成)
gcc -o 可执行文件名 依赖文件集(*.c/*.o) 依赖库文件及其头文件集(由-I或-L与-l指明)
gcc 依赖文件集(*.c/*.o) 依赖库文件及其头文件集(由-I或-L与-l指明) -o 可执行文件名
注意两点:1. gcc永远在首,2. 库永远在依赖文件(*.c/*.cpp/*.o)之后;

gcc编译时链接库选项问题:
gcc -o test -I. str_out.h -L. -lstr_out main.c
无法通过编译,说不能正确链接库函数

而改为
gcc -o test main.c -I. str_out.h -L. -lstr_out
就正确编译了!!!

经查找资料发现,
-l 选项的位置是有意义的,
gcc在处理 -l 选项链接的库的时候,只会查找出现在它前面的文件中所需要链接的符号,如:
gcc -o foo file1.c -lm file2.c
中 gcc 处理 m 库时只会链接 file1.c 中用到的库函数,
而如果 file2.c 中也用到 m 库,它是不能正确链接的。
所以一般将 -l 选项放在 依赖文件集(*.c/*.cpp/*.o) 的后面。

多个库文件要链接时,一定要每个库文件前都有一个 -l 选项!

也可以写成如下形式:
gcc -o test main.c -I. str_out.h -L. ./libstr_out.a
因为,指定 -l 选项 和 指定文件名 的唯一区别是:
-l 选项用 lib 和 .a 或 .so 把 library 包裹起来,而且搜索一些目录。
(默认的库文件位于/usr/lib/或/usr/local/lib/目录中)

原文:http://gcc.gnu.org/onlinedocs/gcc-4.4.2/gcc/Link-Options.html#Link-Options
-llibrary
-l library <-- 是不推荐的
注:当 静态库(libstr_out.a) 和 动态库(libstr_out.so) 同名时, gcc命令将优先使用动态库。

注:无论是静态还是动态库都是由 *.o 目标文件生成,所以第一步都是生成 *.o 目标文件!

一、静态库生成步骤如下:
步骤一:生成 str_out.o 目标文件
gcc -c str_out.c
注:不加 -o 则生成同名的 *.o 目标文件(str_out.o),加上 -o 可以指定生成任意名的目标文件。

步骤二:生成 libstr_out.a 静态库,Linux规定静态库的命名规则一定是以 lib 开头且以 .a 结尾!
ar -crs libstr_out.a str_out.o

ar -cqs libstr_out.a str_out.o
强调一下:ar后面必先是 -crs 或 -cqs 引导的 lib*.a 静态库名,再是 *.o文件名,这个格式是固定的!
     如果写成 ar str_out.o -crs libstr_out.a 或 ar str_out.o -cqs libstr_out.a 都会报如下错误:
      ar: two different operation options specified
注:-c -> 小写字母,如果该库不存在,则有些版本的 ar 必须指定 c 选项,才会进行创建,GNU ar 不需要。
  -r -> 小写字母,将参数 member 中指定的成员插入到归档文件中,替换掉归档文件中原来的同名成员。
              即先删除原来同名的成员,然后将新成员插入到归档文件中。
              默认情况下,新插入的成员会被添加到归档文件的末尾,但可以通过选项`a’, `b’, 或`i’指明插入的位置。

     -a -> 在归档文件中一个已经存在的成员后面增加由参数 member 给出的新文件。
             如果使用选项`a’,则应该为命令行参数 member 指定一个已经存在的成员名(即给出参数 relpos)。

     -b -> 在归档文件中一个已经存在的成员前面增加由参数 member 给出的新文件。
             如果使用任选项`b’,则应该为命令行参数 member 指定一个已经存在的成员名(即给出参数 relpos)。
     -i -> 在归档文件中一个已经存在的成员前面增加一个新的文件。
            如果使用任选项i,则应该为命令行参数 member指定一个已经存在的成员名
           (即给出参数 relpos,类似于选项`b’)。

     -q -> 小写字母,快速追加,即将参数 member 中给出的成员追加到归档文件的末尾,

             而不检查是否要进行替换,也不更新符号表索引。

   -s -> 小写字母,写入一个目标文件索引到归档文件中,或者更新一个存在的目标文件索引。              甚至对于没有任何变化的归档文件也作该动作(等同于对该库做ranlib)。

如果写成这样,r与q共存的话,

ar str_out.o -crqs libstr_out.a

则会出现如下错误提示:

ar: two different operation options specified

以上两步其实已经创建了一个静态库!

 

步骤三:将静态库合并入可执行程序

gcc main.c -o teststaticlib -L. -l str_out

也可写成,如下:

gcc -c main.c -o main.o

gcc main.o -L . -l str_out -o teststaticlib

注:-L -> 大写字母,指定静态库的查找位置,-L后面的.(点)表示静态库在当前目录下查找。

       -l  -> 小写字母,指定静态库名,由于静态库的命名规则是以lib开头且以.a结尾(lib*.a),

                                       故此,lib与.a一定要忽略,如上所示。

                                 如果要合并入多个静态库,则每个静态库名前都要加-l。

  -L与.之间空格可有可无,-l与静态库名之间空格可有可无。

 

nm *.a 命令可以用来列出静态库文件中的符号清单。

 

二、动态库生成步骤如下:

步骤一:生成 str_out.o 目标文件

gcc -c str_out.c -o str_out.o

 

步骤二:生成 libstr_out.so 动态库文件,Linux规定其动态库命名方式为“lib*.so.*”。

    在这个命名方式中,第一个*表示动态链接库的库名,第二个*通常表示该动态库的版本号,

    也可以没有版本号!例如:libc-2.9.so、libc.so.6、libcap.so.2.11。

gcc -shared -fPIC -Wall str_out.o -o libstr_out.so

 

步骤三:将生成的动态库放到系统动态库默认目录(/usr/lib)中去,以便系统可以搜索到它。

sudo cp libstr_out.sousr/lib

注:修改系统目录需要 root 用户权限,

       故此,使用 sudo 命令来前缀 cp 命令,一会系统会让你输入root用户的密码的!

 

步骤四:链接动态库并生成可执行文件

gcc -c main.c -o main.o

gcc main.o -l str_out -o testdynamiclib_1

 

也可以省略第三步用 -Wl,-rpath=./ -L . 在编译时记录运行时搜索动态库的路径,如下:

gcc -c main.c -o main.o

gcc -o testdynamiclib_2 main.o -L . -l str_out -Wl,-rpath,./

写成如下这样也可以:

gcc main.o -Wl,-rpath,./ -L . -l str_out -o testdynamiclib_2

gcc main.o -Wl,-rpath=./ -L . -l str_out -o testdynamiclib_2

强调:1) -Wl,-rpath,动态库路径 或  -Wl,-rpath=动态库路径 这两种写法都可以!

              都是在elf文件中增加记录动态库的搜索路径(除系统默认的外,再搜索指定的)。

          2) 如果少了-L 动态库路径 来指定动态库路径同样编译不过去!

注:当指定多个动态库搜索路径时,路径之间用冒号":"分隔。

       用 readelf -d 可行文件名 命令查看ELF文件的动态节(Dynamic Section)。(如下面所示)

       对比 testdynamiclib_1 和 testdynamiclib_2 的结果我们可以发现,

       testdynamiclib_2 中多出来了RPATH项,指定”Library rpath: [./]”,与可执行文件同一个目录。

       通过这种方式,我们可以用非常小的代价(仅增加几乎可以忽略的空间开销),

       对每个ELF文件都指定最优化的搜索路径,达到提升性能的目的。这是我们比较推荐的一种方法。

 

readelf -d testdynamiclib_1

 

ldd 可执行文件名 -> 查看库链接状况;

objdump <选项> 文件名 -> 对象文件信息;

 

还可以指定要链接的动态库所在的路径,如下:

gcc -c main.c -o main.o

gcc main.o -l str_out -B ./ -o testdynamiclib_2

上下两句意思相同,都可编译通过,但运行时找不到动态库!

gcc main.o -L . -l str_out -o testdynamiclib_2

注:-B -> 大写字母,指定要链接的动态库所在的路径,./ 表示为当前目录。

                                 但是存在一个问题是,编译过去了,但运行时还是找不到动态库!

运行时报错:

./testdynamiclib_2: error while loading shared libraries: libstr_out.so: cannot open shared object file: No such file or directory

 

还可以添加系统变量来指定新的搜索目录路路,如下:

export LD_LIBRARY_PATH=./

注:一旦LD_LIBRARY_PATH被设定,则在这个环境变量生效的范围之内,

  所有其他的ELF可执行程序也会按照这个顺序去搜索动态库,这样势必会造成搜索时的一些浪费。

 

删除添中的系统变量,如下:

unset LD_LIBRARY_PATH

 

也还可以用 LD_PRELOAD 环境变量,它允许你定义在程序运行前优先加载的动态链接库。

这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。

一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码);

另一方面,我们也可以以向别人的程序注入恶意程序,从而达到那不可告人的目的;

在一些UNIX版本上,如果你想要使用LD_PRELOAD环境变量,你需要有root权限。

通过设置执行文件的setgid setuid标志。

在有SUID权限的执行文件,系统会忽略LD_PRELOAD环境变量。

也就是说,如果你有以root方式运行的程序,最好设置上SUID权限。(如:chmod 4755 daemon)

可以通过下面的实例来了解LD_PRELOAD环境变量的使用方法:


  Windows和Linux采用动态链接库技术目的是基本一致的,但由于操作系统的不同,他们在许多方面还是不尽相同,下面从以下几个方面进行阐述。
   1)动态库程序编写,在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数作为初始化的人口,通常在导出函数的声明时 需要有_declspec(dllexport)关键字。Linux下的gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要到函数做特别 声明,编写比较方便。
  2)动态库编译,在windows系统下面,有方便的调试编译环境,通常不用自己去编写makefile文件,但在linux下面,需要自己动手去编写makefile文件,因此,必须掌握一定的makefile编写技巧,另外,通常Linux编译规则相对严格。
  3)动态库调用方面,Windows和Linux对其下编制的动态库都可以采用显式调用或隐式调用,但具体的调用方式也不尽相同。
   4)动态库输出函数查看,在Windows中,有许多工具和软件可以进行查看DLL中所输出的函数,例如命令行方式的dumpbin以及VC++工具 中的DEPENDS程序。在Linux系统中通常采用nm来查看输出函数,也可以使用ldd查看程序隐式链接的共享对象文件。
  5)对操作系统的依赖,这两种动态库运行依赖于各自的操作系统,不能跨平台使用。因此,对于实现相同功能的动态库,必须为两种不同的操作系统提供不同的动态库版本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值