Linux | c语言静态链接库和动态链接库以及动态加载库

  • Post author:
  • Post category:linux


意外的出现了我也可以理解的一节,写个博客记录一下。


  • 基础知识:编译过程

  • 自定义头文件

  • 静态库

  • 动态库



编译过程

C语言的编译链接过程就是由源代码产生可执行文件的过程,主要包括预处理->编译(汇编)->链接三个过程。

预处理

将test.c/cpp/asm文件处理为中间文件,过程包括了对伪指令(宏定义,条件编译,头文件包含指令等以#开头的指令)和特殊符号的处理

编译(汇编)

将中间文件处理为test.o/obj文件

编译程序就是对词法和语法分析,确认合法后将其翻译成等价的中间代码表示或汇编代码,在经过汇编翻译为目标机器指令后,得到可被机器执行的.o文件

链接

由test.o/obj生成test.lib/dll文件

将有关的目标文件彼此连接,如在test.c中使用了math.c中定义的函数add(),链接就是将add()和math.c文件中的定义等画上等号,构成一个可被操作系统装入执行的统一整体,而不是告诉你没有定义这个函数

上述过程都完成后就可以装载(LOAD)将磁盘上的可执行文件test/.exe装入内存中调用进程。

参考:


C/C++程序编译过程详解



自定义头文件

add.h

//这是一个简单的add.h的自定义头文件
#ifndef _ADD_H_

#define _ADD_H_

int add(int a, int b);

#endif

add.c

//这是一个简单的调用了add.h的函数
#include"add.h"

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

add.h中#ifndef #define #endif 就是预处理命令

类似的还有#error #include #pragma …等等以#开头的命令

#define就是宏定义 用标识符来表示字符串,称为“宏” ,被定义为“宏”的标识符称为“宏名”。在编译预处理时,会将程序中所有的宏名用所定义字符串替换,这称为“宏代换”或“宏展开”。

在程序中#include系统提供的头文件(放在/usr/include目录下)使用尖括号(< >),而我们自定义的头文件(一般与C源程序放在同一目录下)在include时使用双引号(” “)。

自定义头文件时要注意,头文件应该只包含函数和变量的声明,不应该包含定义



静态库

库分两种,静态(.a)和动态(.so)

共同点:都是由一个或者多个.o文件打包组成,供其他程序在链接时使用

静态链接库:以lib开头,后缀为.a,当要使用时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。

静态库的构造:

#vi add.h
#vi add.c
#gcc -c add.c //生成add.o文件
#ar cr libadd.a add.o    //由add.o生成libadd.a静态库

到这里就完成了静态库新建所需要的全部操作

新建一个main.c函数并调用libadd.a

#include<stdio.h>
#include"add.h"    //链接libadd.a
//include “add.h"这句话可以替换为:
//extern define add

int main(void)
{
    printf("1 + 2 = %d\n",add(1,2));    
}
//加载
gcc -o main main.c libadd.a    //要将需要调用的库写上

在编译最后加上静态库的名字就可以编译出包含该静态库的二进制可执行文件。

这是在静态库和文件在同一个目录下的情况下,如果要调用的静态库在其他目录,需要加上其路径(相对和绝对都可以)



动态库

某个程序在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了。如果有,则让其共享那一个拷贝;只有没有才链接载入。

在程序运行的时候,被调用的动态链接库函数被安置在内存的某个地方,所有调用它的程序指向这个代码段,库中函数和变量的地址在调用动态库的程序加载时形成,因此,这些代码必须使用相对地址。

在编译的时候,我们需要告诉编译器,这些对象文件是用来做动态链接库的,所以要用地址无关代码(Position Independent Code (PIC))。

我们使用-shared来指定创建的库为动态链接库。

动态库的构造:

#vi sub.h
#ifndef _SUB_H_

#define _SUB_H_

int sub(int a, int b);

#endif

#vi sub.c
#include"sub.h"

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

#gcc -c sub.c
#gcc -shared -fPIC -o libsub.so sub.o    //构造动态库
#cp libsub.so /lib    //把我们的库复制到搜索路径中
#ldconfig -v     //更新一下,并且查看

ldconfig命令是在默认搜寻目录以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库, 进而创建出动态装入程序(ld.so)所需的连接和缓存文件。缓存文件默认为/etc/ld.so.cache, 此文件会保存已排好序的动态链接库名字列表,-v就是查看这个列表,如图,libsub.so库已经在目录中

2

除了将动态链接库放到/lib目录下,另一种链接方式就是在编译时直接指定编译和执行的路径。

这样一个动态链接库就完成了,新建一个主程序调用libsub.so

#include<stdio.h>
#include"sub.h"    //链接libsub.so

int main(void)
{
    printf("1 - 2 = %d\n",sub(1,2));    
}

//加载
gcc -o main main.c libsub.so    //cp方法
//gcc -o main main.c -L./ -libsub.so -WI,-rpath=.       -L指定编译目录为当前目录,-WI,-rpath= 指定运行路径也是当前目录

可能出现的报错


动态加载库


动态加载库,顾名思义,不是在链接时链接,而是在程序运行到了的时候再去加载动态库

动态加载库需要使用dlopen、dlclose、dlsys等动态库函数来控制

#vi multiply.h
#ifndef _MULTIPLY_H_

#define _MULTIPLY_H_

int multiply(int a, int b);

#endif

#vi multiply.c
#include"multiply.h"

int multiply(int a, int b)
{
    return a * b;
}

#gcc -c sub.c
#gcc -shared -fPIC -o libmultiply.so multiply.o    //构造动态库

动态加载库和动态链接库的构建一模一样,主要区别在调用了他们的程序里

#include<stdio.h>
#include <dlfcn.h>    //实现动态链接需要的库
#include"multiply.h"

int main(void)
{
    int (*multiply)();     //函数指针!
    void *fb = NULL;
    char *err = NULL;
    fb = dlopen("./libmultiply.so", RTLD_LAZY);
    if(!fb)
    {
        printf("Failed load library!\n");
    }
    err = dlerror();
        if(err != NULL) {
                printf("%s\n", err);
                return (-1);
        }

        multiply = dlsym(fb, "multiply");
        err = dlerror();
        if(err != NULL) {
                printf("%s\n", err);
                return (-1);
        }

        printf("2 * 3 = %d\n",(*multiply)(2,3));

        dlclose(fb);

     return 0;
}

//加载
gcc -o main main.c -ldl    //-ldl说明需要动态加载

参考资料:



版权声明:本文为frozen122原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。