库的概念
当进行编辑C代码的时候常常会用到printf
函数,这个函数被声明在stdio.h
头文件中。这里需要注意的是声明和定义是两个不同的概念:**定义是指为变量、函数或结构体等分配内存空间并初始化的过程。声明:声明是指告诉编译器某个变量、函数或结构体的类型和名称,但不分配内存空间。声明通常只包含类型和名称,不包含初始值。**例如在stdio.h
头文件中的printf
函数的声明如下:
可以看到在stdio.h
中并没有printf
函数具体的实现方法,printf
函数的实现在/usr/lib
目录下的一个后缀名为.so
的文件里,也就是说函数的具体实现方法对我们而言是不可见的,头文件仅仅是给我们用户提供一个调用接口供用户使用。而在实际使用中,内核已经帮我们去实现了这两者间的映射关系,所以能够通过直接包含头文件就能够使用它里边的函数。
函数库
-
函数库是由系统建立的具有一定功能的函数的集合。库中存放函数的名称和对应的目标代码,以及链接过程中所需的重定位信息。(简单来说就是函数的具体实现方法)
-
Linux中标准的C函数库放置在
/usr/lib
下,通常以后缀名为.so
或.a
文件存放 -
用户也可以根据自己的需要建立自己的用户函数库(通过对目标文件进行创建)
-
函数库分为静态库(
.a
)和共享库(动态库.so
)
库函数
- 存放在函数库中的函数,库函数具有明确的功能、入口调用参数和返回值
- 库函数的源代码一般是不可见的,但在对应的头文件中可以看到对外的接口(函数原型)
一般来说用户都会将源文件编译成目标文件(二进制文件),然后使用工具将目标文件创建静态库或者动态库,这样做的好处一来是可以方便调用,二来是可以保护源代码的知识产权。
静态库的创建与使用
静态库的概念
-
静态库就是一些
.o
目标文件的集合,以.a
结尾 -
静态库在程序链接的时候使用,链接器会将程序中使用到函数代码从库文件中拷贝到可执行文件中。一旦链接完成,在执行程序的时候就不需要静态库了。
有关编译链接的知识可以查看:一个C程序变为一个可执行程序的过程-CSDN博客
-
由于每个使用静态库的应用程序都需要拷贝所用函数的代码,所以生成的可执行文件会比较大
静态库的创建
#ar rcs lib库文件名.a 目标文件1 目标文件2 .... 目标文件n
#- ar: 是归档工具,用于处理静态库和目标文件。
#- rcs: 是归档命令的选项,其中 `r` 表示替换现有库中的同名文件,`c` 表示创建新的库(如果不存在),`s` 表示写入索引到库中。
#- lib库文件名.a: 这是要创建的静态库的文件名,通常以 `.a` 结尾。
#- 目标文件1 目标文件2 .... 目标文件n: 这些是要添加到静态库中的源文件或目标文件。
代码示例–静态库的创建和使用
使用之前的代码,源文件是io.c
,里边包含文件的拷贝,文件权限等函数,目标文件是io.o
文件,最后要被cp.c
这个文件调用实现文件的拷贝这个功能。之前的编译流程是将源文件io.c
编译成目标文件io.o
,然后使用gcc
对cp.c、io.o
指定头文件路径和输出文件的路径,指令如下:
gcc src/cp.c obj/io.o -o bin/cp -I include/
现在使用静态库的方法进行编译执行
静态库的创建
这里使用ar
工具对目标文件创建静态库的时候最好加一个lib
前缀,而且后缀名要以.a
结尾,这样做的目的是为了后边和源文件的编译运行。
静态库的使用
#方法一:
gcc -o 可执行文件 调用者的目标文件(将源文件编译成目标文件后使用) -Ldir -l库文件名
#方法二:
gcc -o 可执行文件 -Idir 调用者的源文件 -Ldir -l库文件名
如图所示,使用后缀名为.o
的目标文件创建成缀名为.a
静态库文件,然后将源文件cp.c
一起编译。
这里需要使用-L
的参数来指定库文件的路径和使用-l
的参数指定要链接到哪一个库,链接库的时候不用写lib
前缀和.a
后缀,直接掐头去尾写中间的名字即可。
-
-Ldir
选项表示将指定的库文件所在目录加入到库搜索路径中,默认的库搜索路径在/usr/lib
目录下 -
-lname
选项(前缀lib
不要加上)表示库搜索路径下的libname.a
或libname.so
文件,这也是为什么库文件都以lib
开头的原因之一,如果库文件不是以lib
开头(例如hello.a
),那么就不能用-l
选项,而是直接写上hello.a
,将原来-lhello
换成hello.a
即可。所以在进行静态库创建的时候前边加lib
前缀算一种规范,方便后边的编译链接操作。 -
-l
是链接器选项,必须放到被编译和链接的文件后面 -
删除静态库文件后不会影响到可执行文件的运行。由于在编译链接的过程中在源文件中用到的静态库中的函数等已经被加入到可执行文件里,所以最后生成的可执行文件和静态库已经没有联系,删掉静态库也并不会影响可执行文件的运行。
共享库的创建和是使用
共享库的概念
- 共享库即动态链接库,在Linux中以
.so
(share object)为后缀,Windows中以.dll
为后缀 - 共享库在程序链接时候并不像静态库那样会拷贝使用函数的代码,而只是做些标记。在程序开始启动(实际上在加载程序时)运行的时候,加载所需的函数。
- 可执行程序在运行的时候仍然需要共享库的支持
- 共享库链接出来的文件比静态库要小得多
- C语言中所有标准的共享库放置在
/usr/lib
目录中
结合上边几句话的概括:当一个可执行程序运行起来就变成了一个进程,而系统会给每一个进程分配一个虚拟内存。其中大部分是用户空间,当共享库被做了标记以后,此时共享库中被标记的函数就会被加载到用户空间的代码段。所以它这个是只有运行的时候才会加载到内存里,正因为这样它的可执行程序会比静态库编译出来的可执行文件会小得多,并且它的每次执行都依赖于动态库,如果动态库删除,可执行文件就不能运行了。
共享库的创建
gcc -shared -fPIC -o lib库文件名.so 目标文件1...目标文件n
#-shared表示是使用共享库
#-fpic或者-fPIC表明创建产生独立目标代码,具体应用取决于平台
共享库的使用
#方法一:
gcc -o 可执行文件 调用者的目标文件 -Ldir -l库文件名
#方法二:
gcc -o 可执行文件 -Idir 调用者的C源文件 -Ldir -l库文件名
-
运行可执行文件出错解决方案(库文件找不到)
解决方法
-
将生成的动态库拷贝到
/usr/lib
目录下去(需要超级用户权限)sudo cp libio.so /usr/lib
-
设置一个临时的环境变量(需要注意的是因为是一个临时的环境变量所以换一个终端或者重启一下就不生效了)
export LD_LIBRARY_PATH=库文件的目录
-
-
删除共享库文件后运行可执行文件出错(提示找不到库文件)
总结
前边说的静态库的链接是将编译好的静态库在链接的过程将所用到的函数等功能复制到可执行文件里去,而动态库的链接是将编译好的动态库做一些标记。当运行可执行程序时,在加载过程中将其标记的函数等功能加载到虚拟内存的代码段中。所以相对比下来通过静态库链接后的可执行文件会比动态库链接后的可执行文件大得多。由于它们实现的原理不一样,所以可执行文件在脱离静态库后还能运行,而动态库不行。
如图所示,首先使用ar
创建一个静态库,然后使用gcc
工具进行链接,最后生成cp1
的可执行文件,然后使用gcc
工具创建一个动态库,然后使用gcc
工具进行链接,最后生成cp2
的可执行文件。然后使用ls -l
对比两个可执行文件可以发现经过静态库链接后的文件大小远大于链接动态库后的文件。并且可以发现静态库删掉以后可执行文件仍然能运行,而动态库不行。