ubuntu下用Gcc编译器编译c语言的静态和动态链接库范例

时间:2022-10-21 02:21:12

我在一个目录下新建hello.h hello.c main.c三个文件,我们需要将hello.c文件编译成静态库以及动态库。在存放文件目录下打开一个终端。

三个文件

hello.h(见程序1)为该函数库的头文件。
hello.c(见程序2)是函数库的源程序,其中包含公用函数hello,该函数将在屏幕上输出”Hello XXX!”。
main.c(见程序3)为测试库文件的主程序,在主程序中调用了公用函数hello。

hello.h

#ifndef HELLO_H
#define HELLO_H

void hello(const char *name);
#endif

hello.c

#include <stdio.h>
#include <hello.h>

//这是要被编成函数库的,可以变成静态函数库和动态函数库
void hello(const char *name){
printf("hello %s.\n",name);
}

main.c

#include <stdio.h>
#include <hello.h>

int main(int argc , char *argv[])
{
hello(every one);
return 0;
}

linux下的库本质上来说是一种可执行代码的二进制形式,可以被操作系统载入内存执行,库有两种:静态库和共享库(动态库)。
二者的不同点在于代码被载入的时刻不同。
静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。创建程序后静态库可以删除也没有影响。
共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。所以共享库搜索路径很重要,如果搜索不到,程序运行时候奔溃。
在linux下,库文件一般放在/usr/lib和/lib和/usr/local/lib下,
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称
动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号, minor是副版本号

无论静态库,还是动态库,都是由.o文件创建的。因此我们必须将源程序hello.c通过gcc先编译成.o文件。

创建静态链接库

第一步就是将hello.c 编译为hello.o文件。
但是遇到第一个问题,就是说找不到hello.h文件。
有两个解决方法。
1、第一个用-I选项指明头文件的路径。如下表示hello.h在当前目录下。gcc -c -I ./ hello.c
2、在/etc/profile文件里面增加include路径搜索目录,

C_INCLUDE_PATH=.
export C_INCLUDE_PATH

加入如下这两行,表示当前目录也是搜索路径。注意要使用 source /etc/profile命令是这个修改生效
注意这里用了gcc参数 -c 而不是-o原因
-c选项的意思是进行编译以及汇集操作,但是不进行链接操作。这是用-c选项的原因
-o选项是三个操作直接执行,不会中断执行;
执行后如下,ls命令输出如下hello.o 文件创建

hello.c  hello.c~  hello.h  hello.h~  hello.o  main.c  main.c~

第二步:有了hello.o文件后就可以创建静态库文件了。
静态库的后缀是.a,它的产生分两步
Step 1.由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表
Step 2.ar命令将很多.o转换成.a,成为静态库
静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为myhello,则静态库文件名就是libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用ar命令。
使用命令

ar cr  libmyhello.a  hello.o

ls后看到生成了libmyhello.a静态库文件

hello.c  hello.c~  hello.h  hello.h~  hello.o  libmyhello.a  main.c  main.c~

第三步:在main程序中使用这个静态链接库。

第一种解决办法
在这里遇到的遇到的问题是找不到静态链接库。所以需要-L选项来指明链接库路径。
使用如下命令生成可执行程序

gcc -o main main.c -L . -lmyhello//这个优先按库名找动态链接库
gcc -o main main.c -L . -static -lmyhello //制定只寻找静态链接库。如果确定是静态链接库建议用第二个命令

-L .表示在当前目录下寻找静态链接库。
-l就表示我这个静态库遵循库默认的命名规则。
也有第二种解决方法。像第一步演示的那样,加入如下表示当前目录是静态链接库的搜索路径

LIBRARY_PATH=.
export LIBRARY_PATH

保存,并用 这个命令使他生效。source /etc/profile

那么用这个命令就可以了。不用-L选项

gcc -o main main.c  -lmyhello

然后 ./main执行main程序,输出如下

cindy@cmachine:~/Cworkspace/lib$ ./main 
hello every one.

然后删除静态链接库libmyhello.a看看效果
可以发现main程序还是可以执行的。说明静态链接库已经编译进main程序里面了,这个静态链接库相比动态链接库最大的区别之一。

创建动态链接库

第一步 跟上面一样,也是创建hello.o文件。
但是有个问题就是要加一个选项参数,不然第二步执行不过。这个需要注意下。
参考http://www.linuxidc.com/Linux/2013-04/83293.htm
gcc -c -fPIC hello.c //这里一定要加上-fPIC选项,不然下一步编译失败
获得hello.o文件
第二步:
将hello.o 文件创建动态链接库
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。

gcc -shared -fPIC -o libmyhello.so hello.o

用ls命令看下动态链接库创建好了没

hello    hello.c~  hello.h~  libmyhello.a   main    main.c~
hello.c hello.h hello.o libmyhello.so main.c main.o

第三步:就是在main程序中使用动态链接库创建可执行程序了。

gcc -o vincent main.c  -lmyhello 
或者
gcc -o vincent main.c -L . -lmyhello
//这个可执行程序只是有库的引用,库代码并没有
//合并到可执行程序里面所以才包下面这个错。这是运行时错误。

会生成vincent程序,但是当./vincent执行这个程序时候。
但是报了一个错说

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

就是说没找到这个动态链接库,我们都是知道动态链接库是只有在程序执行时候才链接这个库,这个时候他是需要知道这个库在哪里的。而这个库在我的目录下,不在系统链接目录下面。所以报错说找不到。
解决办法有三种,
第0种:
哦!出错了。快看看错误提示,原来是找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件进行加载。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件libmyhello.so复制到目录/usr/lib中试试。

cindy@cmachine:~/Cworkspace/lib$ ./vincent 
hello every one.成功。

第一种:
如果共享库文件安装到了/usr/local/lib(很多开源的共享库都会安装到该目录下)或其它”非/lib或/usr/lib”目录下, 那么在执行ldconfig命令前, 还要把新共享库目录加入到共享库配置文件/etc/ld.so.conf中, 如下:

# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
# echo "/usr/local/lib" >> /etc/ld.so.conf
//将新的目录加到共享库搜索路径配置文件哪里去,
//当然可以把当前我们用的库路径用这种形式加进去
# ldconfig //使修改生效

第二种
如果共享库文件安装到了其它”非/lib或/usr/lib” 目录下, 但是又不想在/etc/ld.so.conf中加路径(或者是没有权限加路径). 那可以export一个全局变量LD_LIBRARY_PATH, 然后运行程序的时候就会去这个目录中找共享库.

LD_LIBRARY_PATH的意思是告诉loader在哪些目录中可以找到共享库. 可以设置多个搜索目录, 这些目录之间用冒号分隔开, 比如安装了一个mysql到/usr/local/mysql目录下, 其中有一大堆库文件在/usr/local/mysql/lib下面, 则可以在.bashrc或.bash_profile或/etc/profile和shell里加入以下语句即可:

export LD_LIBRARY_PATH=/usr/local/mysql/lib:$LD_LIBRARY_PATH   

一般来说这只是一种临时的解决方案, 在没有权限或临时需要的时候使用.这里我采用的是第二种方案。
在/etc/profile文件下加如下语句

 LD_LIBRARY_PATH=.
export LD_LIBRARY_PATH

然后运行./vincent就可以啦。输出结果

cindy@cmachine:~/Cworkspace/lib$ ./vincent 
hello every one.

总结

gcc创建静态库和动态库中遇到的挑战主要存在于两个方面
1、gcc一些选项参数不是很熟悉
2、构建库后对库的搜索路径,以及头文件搜索路径都不是很熟悉。

下面对这两个问题都根据所遇到的问题进行简单的解答
第一:gcc选项参数
编译参数解析
最主要的是GCC命令行的一个选项:
编译源文件成为.o文件是需要使用-c 选项而不是-o选项
-shared该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件

-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

-L .:表示要连接的库在当前目录中,对于链接动态库单纯指定这个选项还不行,关键是程序运行时候能在共享库搜索路径上找到这个库。

-ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库文件的名称。

建立共享链接库只需要用GCC的”-shared”标记即可

第二:对于头文件和静态库以及动态库的搜索路径问题,有很多办法做到

默认情况下,gcc在下面目录中搜索头文件:
/usr/local/include/
/usr/include/
在下面目录中搜索库:
/usr/local/lib/
/usr/lib/
/lib
搜索头文件的目录列表常被称为include路径,而搜索库的目录列表被称为搜索路径或链接路径。

我们要扩展头文件的搜索路径
只需要在/etc/profile 下增加下面形式的代码,多个路径之间用:隔开。比如这个只是单纯把当前路径加入了include路径搜索范围。

C_INCLUDE_PATH=.
export C_INCLUDE_PATH

扩展库搜索范围。

可以看我上面给出的三种方法,
1、将库复制到系统搜索路径下
2、更改共享库搜索路径配置文件,将你的共享库目录添加进去。
3、对于静态库和动态库都可以在/etc/profile文件下增加搜索目录,比如下面这样也是可以的。

  #找到静态链接库的路径
LIBRARY_PATH=.
export LIBRARY_PATH

#找到动态链接库的路径
LD_LIBRARY_PATH=.
export LD_LIBRARY_PATH