GCC 编译使用动态链接库和静态链接库的方法

1 库的分类

依据链接时期的不同,库又有静态库和动态库之分。

静态库是在链接阶段被链接的。所以生成的可执行文件就不受库的影响了。即使库被删除了,程序依旧能够成功执行。

有别于静态库,动态库的链接是在程序执行的时候被链接的。所以,即使程序编译完,库仍须保留在系统上,以供程序执行时调用。

2 静态库和动态库的比較

链接静态库事实上从某种意义上来说也是一种粘贴复制。仅仅只是它操作的对象是目标代码而不是源代码而已。由于静态库被链接后库就直接嵌入可运行文件里了,这样就带来了两个问题。

首先就是系统空间被浪费了。这是显而易见的,想象一下,假设多个程序链接了同一个库,则每个生成的可运行文件就都会有一个库的副本,必定会浪费系统空间。

再者,人非圣贤,即使是精心调试的库,也难免会有错。一旦发现了库中有bug,拯救起来就比較麻烦了。必须一一把链接该库的程序找出来。然后又一次编译。

而动态库的出现正弥补了静态库的以上弊端。

由于动态库是在程序执行时被链接的,所以磁盘上仅仅须保留一份副本,因此节约了磁盘空间。假设发现了bug或要升级也非常easy,仅仅要用新的库把原来的替换掉即可了。

那么。是不是静态库就一无是处了呢?

答曰:非也非也。不是有句话么:存在即是合理。

静态库既然没有湮没在滔滔的历史长河中,就必定有它的用武之地。想象一下这种情况:假设你用libpcap库编了一个程序,要给被人执行,而他的系统上没有装pcap库。该怎么解决呢?最简单的办法就是编译该程序时把全部要链接的库都链接它们的静态库。这样。就能够在别人的系统上直接执行该程序了。

所谓有得必有失。正由于动态库在程序执行时被链接。故程序的执行速度和链接静态库的版本号相比必定会打折扣。

然而瑕不掩瑜,动态库的不足相对于它带来的优点在现今硬件下简直是微不足道的,所以链接程序在链接时通常是优先链接动态库的。除非用-static參数指定链接静态库。

3 动态链接库

隐式调用

1. 创建动态链接库

#include<stdio.h>
void hello()
{
printf("hello world/n");
}

用命令gcc -fPIC -shared hello.c -o libhello.so编译为动态库。

能够看到。当前文件夹下多了一个文件libhello.so。

gcc參数

-shared:

该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号)。不用该标志外部程序无法连接。

相当于一个可运行文件

-fpic:

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

2. 再编辑一个測试文件test.c,内容例如以下

#include<stdio.h>
int main()
{
printf("call hello()");
hello();
}

编译 gcc test.c -lhello

-l 选项告诉编译器要使用hello这个库。奇怪的地方是动态库的名字是libhello.so,这里却使用hello.

但这样还不行。编译会出错。

In function `main':

test.c:(.text+0x1d): undefined reference to `hello'

collect2: ld returned 1 exit status

这是由于hello这个库在我们自己的路径中,编译器找不到。

须要使用-L选项,告诉hello库的位置

gcc test.c -lhello -L. -o test

-L .告诉编译器在当前文件夹中查找库文件

3. 编译成功后运行./test, 仍然出错

说找不到库

有两种方法:



一、能够把当前路径增加 /etc/ld.so.conf中然后执行ldconfig。或者以当前路径为參数执行ldconfig(要有root权限才行)。



二、把当前路径增加环境变量LD_LIBRARY_PATH中



当然。假设你认为不会引起混乱的话,能够直接把该库拷入/lib,/usr/lib/等位置(无可避免,这样做也要有权限),这样链接器和载入器就都能够准确的找到该库了。

我们採用另外一种方法:

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

这样,再运行就成功了。

注:

LD_LIBRARY_PATH:该环境变量主要用于指定查找共享库(动态链接库)时除了默认路径之外的其它路径。当运行函数动态链接.so时,假设此文件不在缺省文件夹下‘/lib’
and ‘/usr/lib’.那么就须要指定环境变量LD_LIBRARY_PATH。

假如如今须要在已有的环境变量上加入新的路径名,则採用例如以下方式:
LD_LIBRARY_PATH=NEWDIRS:$LD_LIBRARY_PATH.(NEWDIRS是新的路径串)

显式调用

显式调用须要包括头文件#include <dlfcn.h>。

涉及到以下几个函数:dlopen()、dlsym()、dlerror()、dlclose()。

dlopen()函数以指定模式打开指定的动态链接库文件,并返回一个句柄给dlsym()的调用进程。

使用dlclose()来卸载打开的库。当动态链接库操作函数运行失败时,dlerror能够返回出错信息,返回值为NULL时表示操作函数运行成功。

编译时候要增加 -ldl (指定dl库)

详细的函数原型例如以下:

void *dlopen(const char *filename, int flag);

char *dlerror(void);

void *dlsym(void *handle, const char *symbol);

int dlclose(void *handle);

dlopen以指定模式打开指定的动态连接库文件。并返回一个句柄给调用进程,dlerror返回出现的错误,dlsym通过句柄和连接符名称获取函数名或者变量名,dlclose来卸载打开的库。

如果已经生成libcaculate.so库,里面定义了add(),sub(),mul(),div()等函数。这里给出调用演示样例:

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h> //动态链接库路径
#define LIB_CACULATE_PATH "./libcaculate.so" //函数指针
typedef int (*CAC_FUNC)(int, int); int main()
{
void *handle;
char *error;
CAC_FUNC cac_func = NULL; //打开动态链接库
handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
} //清除之前存在的错误
dlerror(); //获取一个函数
*(void **) (&cac_func) = dlsym(handle, "add");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("add: %d\n", (*cac_func)(2,7)); cac_func = (CAC_FUNC)dlsym(handle, "sub");
printf("sub: %d\n", cac_func(9,2)); cac_func = (CAC_FUNC)dlsym(handle, "mul");
printf("mul: %d\n", cac_func(3,2)); cac_func = (CAC_FUNC)dlsym(handle, "div");
printf("div: %d\n", cac_func(8,2)); //关闭动态链接库
dlclose(handle);
exit(EXIT_SUCCESS);
}

4 静态链接库

仍使用刚才的hello.c和test.c。

1. gcc -c hello.c 注意这里没有使用-shared选项

2. 把目标文件归档    ar -r libhello.a hello.o

    程序 ar 配合參数 -r 创建一个新库 libhello.a 并将命令行中列出的对象文件插入。採用这样的方法,假设库不存在的话,參数 -r 将创建一个新的库。而假设库存在的话,将用新的模块替换原来的模块。

3. 在程序中链接静态库

           gcc test.c -lhello -L. -static -o hello.static

或者   gcc test.c libhello.a -L. -o hello.static

生成的hello.static就不再依赖libhello.a了LD_LIBRARY_PATH

上一篇:【WinForm】使用NSIS发布程序


下一篇:Git常用操作汇总(转)