C语言库的创建和调用
简介:
假如,你有一个庞大的工程,代码量达到数百兆甚至是数G,你经常会遇到好多重复或常用的地方。每次使用到这些地方时如果都重新写一份基本相同的代码,这当然可以,不过这样会大大地降低工作效率,而且影响代码的可读性。更不好的是日后的修改工作会使你变得非常的繁琐,这样很不利于后期的维护。如果把这些相同的功能代码分别以模块的形式存放起来,把他们编译成库,使用时直接调用他们的库,这样直接使用起来非常的方便,更有利于代码的维护和升级。
库的概念:
库是由源代码编译出来的,是对一组源文件编译出来的中间文件。使用库可以做到不开放源代码,同时令其他单元可以调用到的效果,实现良好的接口封装。
其实,现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。比如我们常使用的printf函数,就是c标准库提供的函数。我们在使用时只需要包含相应的头文件就可以使用(非静态编译还要有相应的库文件)。而不用关心printf函数具体是如何实现的,这样就大大提高了程序员编写代码的效率。
库的分类:
库大体上可以分为两类:静态库和动态库。在windows中静态库是以 .lib 为后缀的文件,共享库是以 .dll 为后缀的文件。在linux中静态库是以 .a 为后缀的文件,动态库是以 .so为后缀的文件。
生成两种库的方式:
生成静态库:
首先将源文件编译成目标文件: gcc -c a.c b.c
然后将目标文件链接生成静态库: ar -rc libstatic.a a.o b.o
生成动态库:
首先将原文件编译成目标文件: gcc -fPIC -c a.c b.c
然后将目标文件链接生成动态库 gcc -fPIC --shared -o libshared.so a.o b.o
当然也可以一步生成: gcc -fPIC --shared -o libshared.so a.c b.c
库的链接方法
静态库的链接方法:
gcc -o static main.c -L./ -lstatic -static
动态库的链接方法:
gcc -o shared main.c -L./ -lshared
静态库和动态库各有各的特点,总的来说:静态库利用空间效率换取时间效率;动态库则利用时间效率换取空间效率。
静态库和动态库有个地方需要注意的:当程序与静态库连接时,库中文件所含的所有程序将被程序的使用函数复制到最终代码文件中;当程序与动态库链接时可执行文件只包含它需要的函数的引用表,而不是所有的函数的代码,只有程序在执行时,才将需要的代码拷贝到内存中,明显的利用时间来换取空间。这是需要注意的是:一个程序编译好了后,当需要修改的刚好是库函数时,在接口不变的情况下,使用动态库的只需要将动态库重新编译一遍就行了,而使用静态的的程序则需要将静态库重新编译好后,同时将调用的程序也编译一遍。
使用动态库需要注意的事项:
使用静态库是在可执行程序链接是就已经加入代码中了,在物理上成为代码的一部分,因此使用静态库编译的程序运行时告知在哪里能找到库。而动态库是在程序娙时才加载到程序中的,运行时,机器需要找到库将代码补充完整。
使用动态库的方法和注意事项:如果程序连接时使用了动态库,就必须在程序运行时能够找到动态库的位置。Linux的可执行程序在执行的时候默认先搜索/lib和/usr/lib这两个目录,然后按照/etc/ld.so.conf里面的配置搜索绝对路径。同时Linux也提供了环境变量LD_LIBRARY_PATH (如:
export LD_LIBRARY_PATH=/home/Linux/exercise/library/file2)供用户选择使用,用户可以通过它来设定它查找出默认路径外的其他路径。不过,LD_LIBRARY_PATH的设定作用是全局的,过多的设定可能会影响其他程序的运行。有些系统的gcc支持-R或-rpath选项,多建议使用这种方法和/etc/ld.so.conf或在/etc/ld.so.conf.d目录下新建一个.conf文件然后添加动态库的搜索路径,尽量避免使用LD_LIBRARY_PATH。
需要注意的是:添加路径需要在终端运行命令:ldconfig
本博客小结下C语言如何产生库让C调用和让C++调用。
例:library目录下有file1、file2、file3和LIB三个目录,如下:
library --file1 file1.c hello.h hello.c
--file2 file2.c hi.c hi.h
--file3 file3.h file3.cpp
--LIB
在当前目录下生成库调用
”这是个C语言生成C语言库给C语言调用的简单例子“
[root@centos-64-min file1]# cat file1.h
void play1();
[root@centos-64-min file1]# cat file1.c
#include<stdio.h>
#include"file1.h"
int main(void)
{
play1();
return 0;
}
[root@centos-64-min file1]# cat play1.c
#include<stdio.h>
void play1(void)
{
printf("hello file1 ");
}
使用静态库:
编译生成目标文件 gcc -c play.c
链接生成静态库 ar -rc libstatic.a play1.o
使用静态库编译 gcc -o staticfile file1.c -L/home/Linux/exercise/library/file1 -lstatic
运行结果:
[root@centos-64-min file1]# ./staticfile
hello file1
使用动态库:
使用静态库是在可执行程序链接是就已经加入代码中了,在物理上成为代码的一部分,因此使用静态库编译的程序运行时告知在哪里能找到库。而动态库是在程序娙时才加载到程序中的,运行时,机器需要找到库将代码补充完整。
使用动态库的方法和注意事项:如果程序连接时使用了动态库,就必须在程序运行时能够找到动态库的位置。Linux的可执行程序在执行的时候默认先搜索/lib和/usr/lib这两个目录,然后按照/etc/ld.so.conf里面的配置搜索绝对路径。同时Linux也提供了环境变量LD_LIBRARY_PATH (如:
export LD_LIBRARY_PATH=/home/Linux/exercise/library/file2)供用户选择使用,用户可以通过它来设定它查找出默认路径外的其他路径。不过,LD_LIBRARY_PATH的设定作用是全局的,过多的设定可能会影响其他程序的运行。有些系统的gcc支持-R或-rpath选项,多建议使用这种方法和/etc/ld.so.conf或在/etc/ld.so.conf.d目录下新建一个.conf文件然后添加动态库的搜索路径,尽量避免使用LD_LIBRARY_PATH。
需要注意的是:添加路径需要在终端运行命令:ldconfig
[root@centos-64-min file2]# cat file2.c
#include<stdio.h>
#include“hi.h”
int main(void)
{
hi();
return 0;
}
[root@centos-64-min file2]# cat hi.h
#include“hi.c”
void hi();
[root@centos-64-min file2]# cat hi.c
#include<stdio.h>
void hi(void)
{
printf("hi file2
");
}
编译生成目标文件: gcc -fPIC -c hi.c
链接生成动态库: gcc -fPIC -shared -o libshared1.so hi.o
在配置文件中添加搜索路径 或
在终端运行命令: ldconfig
[root@centos-64-min file1]# gcc -g -o sharedfile file2.c -L/home/Linux/exercise/library/file2 -lshared1
[root@centos-64-min file1]# ./sharedfile
hello file1
在file1调试的其他一些小问题:
问题一:
[root@centos-64-min file1]# gcc --shared play1.c -o libplay1.so
/usr/bin/ld: /tmp/ccX7Elnx.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
/tmp/ccX7Elnx.o: could not read symbols: Bad value
collect2: ld returned 1 exit status
解决方法:
[root@centos-64-min file1]# gcc -fPIC --shared play1.c -o libplay1.so
问题二:
[root@centos-64-min file1]# gcc -o file1 file1.c libplay1.so
[root@centos-64-min file1]# ./file1
./file1: error while loading shared libraries: libplay1.so: cannot open shared object file: No such file or directory
出现这种情况主要是因为执行文件找不到 play1库,致使链接不到,在编译时指明库所在的位置就可以了。
解决方法:
[root@centos-64-min file1]# gcc -o file1 file1.c ./libplay1.so
[root@centos-64-min file1]# ./file1
hello file1
运行结果:
[root@centos-64-min file2]# gcc -o file2 file2.c
[root@centos-64-min file2]# ./file2
hi file2
把file1和file2生成的库存放到LIB中
[root@centos-64-min file2]# cp libfile2.so ../LIB/
[root@centos-64-min file1]# cp libplay1.so ../LIB/
[root@centos-64-min LIB]# ls
libfile2.so libplay1.so
在file3中编写c++文件调用LIB中库
[root@centos-64-min file3]# cat file3.cpp
#include<iostream.h>
int main(void)
{
play1();
hi();
return 0;
}
[root@centos-64-min file3]# cat file3.h
#ifdef __cplusplus
extern "C"
{
#endif
void hi();
void play1();
#ifdef __cplusplus
}
#endif
[root@centos-64-min file3]# g++ -o file3 file3.h file3.cpp ../LIB/libfile2.so ../LIB/libplay1.so
[root@centos-64-min file3]# ./file3
hello file1
hi file2
c++调用C的库
首先,先了解
extern “C”的理解:
extern “C”指令描述的是一种链接约定,其并不影响调用函数的定义。即使使用了该声明,对函数的检查和参数的转换仍然遵循C++的规则。
extern "C"的作用:
不同的语言链接性是不同的,这样也决定了他们编译后的链接符号的不同,例如,函数void fun(double a),C语言编译后是_func这样的符号,C链接器只要找到这样的符号就能够成功地链接,它默认假设参数类型信息是正确的。而C++会把它编译成 _fun_doublle 或 _xxxfunDxxx这样的符号,在C语言编辑器的符号基础上增加了类型信息。这也是C++可以有重载函数的原因。
介于此,对于用C编辑器编译出来的库,用C++直接链接势必会出现不能识别符号的问题,这时候就需要用 extern "C" 来告诉编译器要以C语言的方式编译和链接该封装函数。
上面file3文件里的例子就是c++调用C的一个示例。
__cplusplus宏的条件编译
为什么要加这个编译条件呢,因为extern "C"语法在C编译环境下是不允许的。这样这的写法防止当这个文件被用作C文件编译时,可以去掉C++结构,也就是说,防止test.cpp改为test.c,#include<iostream>改为#include<stdio.h>时,C编译器也能能够顺利的编译通过
这种技术也可能会用在由C头文件和C++头文件中,这样使用是为了建立起公共的C和C++文件,也就是保证当这个文件被用做C文件编译时,可以去掉C++结构,成功编译。
比如:将上面的file3.cpp更名为file3.c,将头文件改为stdio.h,将条件编译去掉,再用gcc编译也能正确地编译。而即使做了上面的修改,这样就能公共地为C和C++文件使用
有这个条件预编译的结果:
[root@centos-64-min file3]# gcc -o file3 file3.h file3.c ../LIB/libfile2.so ../LIB/libplay1.so
[root@centos-64-min file3]# ./file3
hello file1
hi file2
没有这个条件编译的结果:
[root@centos-64-min file3]# gcc -o file3 file3.h file3.c ../LIB/libfile2.so ../LIB/libplay1.so
file3.h:2: error: expected identifier or ‘(’ before string constant
In file included from file3.c:2:
file3.h:2: error: expected identifier or ‘(’ before string constant