Linux 之三 静态库及动态库的编写和使用

  最近在整理旧电脑时,发现了一些刚入行时的学习记录,以及最早使用新浪博客 http://blog.sina.com.cn/zcshou 写的一些文章。最近要重拾 Linux,所以把这些 Word 文档重新排版转到此博客上,一来复习一下,二来在 CSDN 上作个存档!
在这里插入图片描述
至于新浪博客上的文章就留那里吧!话说新浪博客是不是块倒闭了?还有一点,如果没记错,文章里使用的是 CentOS。

  注意,这些文章里的内容多数可能来自网络(当初学习时翻看了各种网络资料)但应该不是原版抄袭(作为一名工科生,不动手实践怎么能行!)。如果您发现其中内容有侵权,请私信我,我将立刻处理!

ELF 文件规范

  ELF(Executable and Linking Format)是一个二进制文件规范。用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。

  现在流行的二进制可执行文件格式 (Executable File Format),主要是 Windows 下的 PE(Portable Executable)和 Linux 的 ELF(Executable and Linking Format)可执行和链接格式)。他们都是 COFF(Common Object File Format)的变种。ARM 体系中采用的也是 ELF 文件格式。

  COFF 是在 Unix System V Release 3 时由 UNIX 系统实验室(UNIX System Laboratories, USL)首先提出并且使用的文件规范,后来微软公司基于 COFF 格式,制定了 PE 格式标准,并将其用于当时的 Windows NT 系统。在 System V Release 4 时,UNIX 系统实验室在 COFF 的基础上,开发和发布了 ELF 格式,作为应用程序二进制接口 (Application Binary Interface,ABI)。

  此后,工具接口标准委员会(Tool Interface Standard Committee,TISC)选择了正在发展中的 ELF 标准作为工作在 32 位 INTEL 体系上不同操作系统之间可移植的二进制文件格式。可以从这里 找到详细的标准文档。如下图:
在这里插入图片描述
TISC 共出过两个版本(v1.1和 v1.2)的标准文档。两个版本内容上差不多,但 v1.2 版本重新组织了原本在 v1.1 版本中的内容。可读性更高。两个版本的目录如下所示:
在这里插入图片描述
在 ELF 文件规范中,把系统中采用 ELF 格式的文件(规范中称为对象文件(Object File))归类为以下三种:

  • 可重定位文件(Relocatable File ): 这类文件包含代码和数据,可用来连接成可执行文件或共享对象文件(Object File),静态链接库归为此类,对应于 Linux 中的 .o ;Windows 的 .obj.
  • 可执行文件(Executable File ): 这类文件包含了可以直接执行的程序,它的代表就是 ELF 可执行文件。Linux 下,他们一般没有扩展名,比如 /bin/bash;Windows 下的 .exe
  • 共享对象文件(Object File)(Shared Object File ): 这种文件包含代码和数据,链接器可以使用这种文件跟其他可重定位文件的共享对象文件(Object File)链接,产生新的对象文件(Object File)。对应于Linux 中的 .so,Windows 中的 DLL
    另外是动态链接器可以将几个这种共享对象文件(Object File)与可执行文件结合,作为进程镜像文件来运行。

  在 Linux 系统中,还有一类文件,被称为核心转储文件(Core Dump File) ,当进程意外终止,系统可以将该进程地址空间的内容及终止时的一些信息转存到核心转储文件。 对应 Linux 下的 core dump。

库文件

  为了避免一些重复性的工作并且便于编程,开发人员定义了一系列的标准函数以供调用,这些函数都放在相应的库中,而我们在进行开发时直接使用这些标准函数。

  库文件就是在系统层面对于 ELF 各种文件的称呼。从本质上来说就是一种可执行代码的二进制格式,可以被载入内存中执行。库分静态库和动态库两种。

静态库

  在编译目标程序时,所使用的静态库中的函数的所有数据都会被整合进目标代码中,编译出的目标程序的执行程序不需要外部的函数库支持。linux 中静态库文件的扩展名一般为 .a,a 就是 archive 的缩写。其编写步骤很简单:

  1. 编写函数代码
  2. 编译生成各目标文件
  3. ar 文件对目标文件归档,生成静态库文件。注意:归档文件名必须以 lib 打头。
  4. 使用要点:
    1. gcc-I 参数后加上静态库头文件的路径
    2. gcc-L 参数后加上库文件所在目录
    3. gcc-l 参数后加上库文件名,但是要去掉 lib 和 .a 扩展名。比如库文件名是 libtest.a,那么参数就是 -ltest
  5. 静态库必须按照 lib[name].a 的规则命名

编写最简单的静态库文件

  1. 编写如下两个文件,注意放在同一目录中
    • myalib.h 文件的内容
      void test();
      
    • myalib.c 文件的内容
      #inlcude <stdio.h>
      void test()
      {
      	printf("test\n");
      }
      
  2. 制作库文件
    1. 生成目标文件。执行命令 gcc -c myalib.c,其中:-c 表示只编译,不链接 myalib.c,执行完后会生成一个 myalib.o 文件
    2. ar 命令归档。格式为 ar -rc <生成的档案文件名> <.o文件名列表>。归档文件名一定要以 lib 打头及 .a 结尾。ar -rc libtest.a myalib.o,执行完后会生成一个 libtest.a 文件
  3. 使用库文件
    1. 编写一个测试程序 main.c,内容为
         //main.c 测试静态库调用的程序
         #include "myalib.h"   //要把函数的头文件包含进来,否则编译时会报错
         int main(int argc,char* argv[])
         {
             test();
             return 0;  
         }
      
    2. 编译目标文件。注意要把静态库头文件的路径加到 -I 参数里面。gcc -I ~ -o main.o -c main.c。其中:-o 表示输出文件 mian.o-c 表示只编译 main.c 不进行链接。现在生成了一个 main.o 文件。注意: 表示库文件在当前目录下,根据自己目录修改。
    3. 生成可执行文件。注意要把静态库文件的路径加到 -L 参数里面,把库文件名(去掉打头的 lib 和结尾的 .a )加到 -l 参数后面。执行命令 gcc -o main -L~ main.o -ltest 后会生成一个名为 main 的可执行文件 注意: 表示库文件在当前目录下,根据自己目录修改。另外,注意 -l 参数好象应该加到输入文件名的后面,否则会报错。
      在这里插入图片描述
  4. 执行可执行文件 ./main 查看效果, 如下:
    在这里插入图片描述
    说明执行成功。

动态库

  动态库也有的称为共享库。在编译目标程序的时候,其使用的动态函数库不会被编译进目标程序中,编译生成的目标程序执行到相关函数时必须要调用所使用的动态库中的对应函数。在 linux 中,动态库一般以 .so 结尾,so 就是 shared object 的缩写。其基本生成步骤为:

  1. 编写函数代码
  2. 编译生成动态库文件,要加上 -shared-fpic 选项 ,动态库文件名以 lib 开头, 以 .so 结尾。
  3. 使用方式分为两种: 隐式调用和显示调用
    • 隐式调用:类似于静态库的使用,但需修改动态链接库的配置文件 /etc/ld.so.conf;
    • 显示调用:则是在主程序里使用 dlopen、dlsym、dlerror、dlclose 等系统函数。

编写最简单的动态库文件

  1. 编写如下两个文件,注意放在同一目录中
    1. myalib.h 文件的内容
      void test();
      
    2. myalib.c 文件的内容
      #inlcude <stdio.h>
      void test()
      {
      	printf("test\n");
      }
      
  2. 使用命令:gcc -fpic -shared -o libtest.so myalib.c 编译生成动态库,库文件名以 lib 开头, 以 .so 结尾。此时就生成一个 libtest.so 文件

隐式调用

  隐式调用的含义是代码里不出现库文件名,使用方式和调用静态库的代码是类似的。下面我们以一个示例来说明一下。

  1. 编写测试文件
    //main.c 测试动态库隐式调用的程序
    #include "myalib.h"   // 要把函数的头文件包含进来,否则编译时会报错
    int main(int argc,char* argv[])
    {
    	test();
    	return 0;  
    }
    
  2. 使用命令:gcc -I ~ -o main.o -c main.c 编译测试程序,与静态库类似,要把头文件的路径加到 -I 参数里面。现在生成了一个 main.o 文件
  3. 使用命令:gcc -o main -L~ main.o -ltest 连接生成测试程序。此时会生成了一个 main 文件
  4. 执行测试程序 ./main。不出意外的话,应该会报错。这是由于 linux 查找动态库的方式导致的。此时,可以使用命令:LD_LIBRARY_PATH=. ./mian 来运行。

显式调用

  显式调用的含义是代码出现库文件名,用户需要自己去打开和管理库文件。注意以下两点:

  1. 需要包含 dlfcn.h 系统头文件
  2. dlopen 函数打开库文件,并指定打开方式。

下面以示例来说明一下:

  1. 编写测试文件
    #include <dlfcn.h>  //用于动态库管理的系统头文件
    #include "myalib.h" //要把函数的头文件包含进来,否则编译时会报错
    int main(int argc,char *argv[])
    {
        //声明对应的函数的函数指针
        void(pTest)();
        //加载动态库
        void pdlHandle = dlopen("libtest.so", RTLD_LAZY);
        //错误处理
        if (pdlHandle == NULL)
        {
            printf("Failed load library\n");
            return -1;
       }
        char pszErr = dlerror();
        if (pszErr != NULL)
        {
            printf("%s\n", pszErr);
            return -1;
        }
        //获取函数的地址
        pTest = dlsym(pdlHandle, "test");
        pszErr = dlerror();
        if (pszErr != NULL)
       {
           printf("%s\n", pszErr);
            dlclose(pdlHandle);
            return -1;
        }
        //实现函数调用
        (pTest)();
        //程序结束时关闭动态库
        dlclose(pdlHandle);
        return 0;
    }
    
  2. 使用命令 gcc -o main -ldl main.c 编译测试文件。使用 -ldl 选项指明生成的对象模块需要使用共享库。执行完后就生成了一个 main 文件.
  3. 执行测试程序。执行 ./main

参考

  这篇文章中的内容,应该参考了一篇百度博客。现在百度博客关闭了,无从查找作者。如果有知道的请私信我,谢谢!

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZC·Shou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值