UNIX-Linux环境编程(二):静态库与动态库

一、库

1.合久必分——增量编译——易于维护,分久必合——库——易于使用。
2.链接静态库是将库中的被调用代码复制到调用模块中, 而链接共享库则只是在调用模块中,嵌入被调用代码在库中的(相对)地址。
3.静态库占用空间非常大,不易修改但执行效率高。共享库占用空间小,易于修改但执行效率略低。
4.静态库的缺省扩展名是.a,共享库的缺省扩展名是.so。
5.环境变量

C_INCLUDE_PATH     - C头文件的附加搜索路径,相当于gcc的-I选项
CPATH  - 同C_INCLUDE_PATH
CPLUS_INCLUDE_PATH - C++头文件的附加搜索路径
LIBRARY_PATH       - 链接时查找静态库/共享库的路径
LD_LIBRARY_PATH    - 运行时查找共享库的路径

•通过gcc的-I选项指定C/C++头文件的附加搜索路径:

# gcc calc.c cpath.c -I.

•将当前目录作为C头文件附加搜索路径,添加到CPATH环境变量中:

# export CPATH=$CPATH:. // export保证当前shell的,子进程继承此环境变量
# echo $CPATH
# env | grep CPATH

•也可以在 ~/.bashrc 或者 ~/.bash_profile,配置文件中写环境变量,持久有效:

export CPATH=$CPATH:.
执行
# source ~/.bashrc 或 # source ~/.bash_profile
//生效,以后每次登录自动生效。

头文件的三种定位方式

1) #include "目录/xxx.h" - 头文件路径发生变化,需要修改源程序
2) C_INCLUDE_PATH/CPATH=目录 - 同时构建多个工程,可能引发冲突
3) gcc -I目录 - 既不用改程序,也不会有冲突

二、静态库

1. 创建静态库

  1. 编辑源程序:.c/.h

  2. 编译成目标文件:gcc -c xxx.c -> xxx.o

  3. 打包成静态库文件:ar -r libxxx.a xxx.o …

     1.# gcc -c calc.c
     2.# gcc -c show.c
     3.# ar -r libmath.a calc.o show.o
    

ar指令:ar [选项] 静态库文件名 目标文件列表

扫描二维码关注公众号,回复: 11360832 查看本文章
-r - 将目标文件插入到静态库中,已存在则更新 
-q - 将目标文件追加到静态库尾 
-d - 从静态库中删除目标文件 
-t - 列表显示静态库中的目标文件 
-x - 将静态库展开为目标文件 

注意:提供静态库的同时也需要提供头文件

2. 调用静态库

  1. 直接调用

     # gcc main.c libmath.a
    
  2. 或通过LIBRARY_PATH环境变量指定库路径

     # export LIBRARY_PATH=$LIBRARY_PATH:.
     # gcc main.c -lmath (环境法)
    
  3. 通过gcc的-L选项指定库路径

     # unset LIBRARY_PATH
     # gcc main.c -lmath -L. (参数法)
    

一般化的方法:gcc .c/.o -l<库名> -L<库路径>

3. 运行

在可执行程序的链接阶段,已将所调用的函数的二进制代码,复制到可执行程序中,因此运行时不需要依赖静态库。
范例:show.h、show.c、calc.h、calc.c、math.h、main.c
show.h

#ifndef _SHOW_H
#define _SHOW_H

void show (int a, char op, int b, int c);   

#endif//_SHOW_H

show.c

#include <stdio.h>
#include "show.h"

void show (int a, char op, int b, int c) 
{
    printf ("%d %c %d = %d\n", a, op, b, c);
}

calc.h


#ifndef _CALC_H
#define _CALC_H

int add (int a, int b);
int sub (int a, int b);

#endif//_CALC_H

calc.c

#include "calc.h"

int add (int a, int b) 
{
    return a + b;
}

int sub (int a, int b)
{
    return a - b;
}

math.h

#ifndef _MATH_H
#define _MATH_H

#include "calc.h"
#include "show.h"

#endif//_MATH_H

main.c

#include "math.h"

int main (void) 
{
    show (30, '+', 20, add (30, 20));
    show (30, '-', 20, sub (30, 20));
}

4. 静态链接库的优缺点

优点:

(1) 代码装载速度快,执行速度略比动态链接库快。

(2) 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。

缺点:

使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费。

三、共享库

1. 创建共享库

  1. 编辑源程序:.c/.h

  2. 编译成目标文件:gcc -c -fpic xxx.c -> xxx.o

  3. 链接成共享库文件:gcc -shared xxx.o … -o libxxx.so

     # gcc -c -fpic calc.c
     # gcc -c -fpic show.c
     # gcc -shared calc.o show.o -o libmath.so
     //或一次完成编译和链接
     # gcc -shared -fpic calc.c show.c -o libmath.so
    

PIC (Position Independent Code):位置无关代码。可执行程序加载它们时,可将其映射到其地址空间的任何位置。
-fPIC : 大模式,生成代码比较大,运行速度比较慢,所有平台都支持。
-fpic : 小模式,生成代码比较小,运行度比较快,仅部分平台支持。
注意:提供共享库的同时也需要提供头文件

2. 调用共享库

  1. 直接调用

     # gcc main.c libmath.so
    
  2. 通过LIBRARY_PATH环境变量指定库路径

     # export LIBRARY_PATH=$LIBRARY_PATH:.
     # gcc main.c -lmath (环境法)
    
  3. 通过gcc的-L选项指定库路径

     # unset LIBRARY_PATH
     # gcc main.c -lmath -L. (参数法)
    

一般化的方法:gcc .c/.o -l<库名> -L<库路径>

3. 运行

运行时需要保证LD_LIBRARY_PATH,环境变量中包含共享库所在的路径。
在可执行程序的链接阶段,并不将所调用函数的二进制代码复制到可执行程序中。
而只是将该函数在共享库中的地址嵌入到可执行程序中,因此运行时需要依赖共享库。
gcc缺省链接共享库,可通过-static选项强制链接静态库。

四、动态加载共享库

	#include <dlfcn.h>

1. 加载共享库

void* dlopen (const char* filename,    int flag);
返回值:
成功返回共享库句柄,失败返回NULL。
参数:
filename:共享库路径
  	若只给文件名,则根据LD_LIBRARY_PATH环境变量搜索。
flag取值:加载方式
 	RTLD_LAZY - 延迟加载,使用共享库中的符号 (如调用函数)时才加载。
 	RTLD_NOW  - 立即加载。

2. 获取函数地址

void* dlsym (void* handle,const char* symbol );
返回值:
 成功返回函数地址,失败返回NULL。
参数:
   hanedle:共享库句柄
   symbol:函数名

3. 卸载共享库

int dlclose (void* handle);
成功返回0,失败返回非零。

4. 获取错误信息

char* dlerror (void);
有错误发生则返回错误信息字符串指针,否则返回NULL。

范例:load.c

#include <stdio.h>
#include <dlfcn.h>

typedef int (*PFUNC_CALC) (int, int);
typedef void (*PFUNC_SHOW) (int, char, int, int);

int main (void) 
{
    void* handle = dlopen ("shared/libmath.so", RTLD_NOW);
    if (! handle) 
    {
        fprintf (stderr, "dlopen: %s\n", dlerror ());
        return -1;
    }

    PFUNC_CALC add = (PFUNC_CALC)dlsym (handle, "add");
    if (! add) 
    {
        fprintf (stderr, "dlsym: %s\n", dlerror ());
        return -1;
    }

    PFUNC_CALC sub = (PFUNC_CALC)dlsym (handle, "sub");
    if (! sub) 
    {
        fprintf (stderr, "dlsym: %s\n", dlerror ());
        return -1;
    }

    PFUNC_SHOW show = (PFUNC_SHOW)dlsym (handle, "show");
    if (! show) 
    {
        fprintf (stderr, "dlsym: %s\n", dlerror ());
        return -1;
    }

    show (30, '+', 20, add (30, 20));
    show (30, '-', 20, sub (30, 20));

    if (dlclose (handle)) 
    {
        fprintf (stderr, "dlclose: %s\n", dlerror ());
        return -1;
    }
    return 0;
}

注意:链接时不再需要-lmath,但需要-ldl。

5. 静态链接库的优缺点

优点:

(1) 更加节省内存并减少页面交换。

(2) DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性。

(3) 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数。

(4)适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。

缺点:

(1)使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败。
(2)速度比静态链接慢。
(3)当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件将无法运行。这在早期Windows中很常见。

猜你喜欢

转载自blog.csdn.net/perror_0/article/details/106807030