欢迎光临
我们一直在努力

自制 Linux 库文件:从编译.o 到部署.a/.so 的完整流程

一、什么是库

库的本质

  • 不是可执行程序(不能直接运行)
  • 是二进制代码.o文件集合
  • 程序运行或编译时被调用
  • 目的:复用、简化开发、提高效率

库的两种类型

1. 静态库(.a)

  • 编译时直接把库代码复制到你的程序里
  • 优点:运行不依赖外部文件
  • 缺点:程序体积大,多个程序共用会冗余

2. 动态库(.so)

  • 编译时只记录库的名字和位置
  • 运行时才去系统里找并加载
  • 优点:体积小、多个程序共用节省内存
  • 缺点:运行必须依赖库存在

注意:自己写的函数 .c 文件是源代码,不是库。把它编译成.o文件、打包成 .a 或 .so 之后,才是库。

.so = Shared Object → 共享、动态

.a = Archive → 归档、静态

查看库依赖

查看可执行程序依赖哪些动态库

ldd 可执行文件名

作用:看程序运行需要哪些 .so 动态库。

在这里插入图片描述

查看文件的属性:

file 文件名

在这里插入图片描述

二、静态库

静态库的制作

1. 将所有.c文件编译成.o文件

gcc -c *.c

在这里插入图片描述

2. 打包归档

ar -rcs libmylib.a *.o

rcs:

  • r:replace —— 有同名文件就替换
  • c:create —— 创建库文件(不存在就新建)
  • s:symbol index —— 生成符号表(symbol index),让查找函数更快

libmylib.a:

  • 必须以 lib 开头
  • 后缀必须是 .a
  • 中间 mylib 是你自己起的库名

在这里插入图片描述

3. .h 头文件不参与打包

.h 头文件单独保存,不在.a 库里面,编译时,.h最好和.c同目录。

库文件里不能有main函数。

一般库发布包

mylib/
├── lib
│└── libmylib.a # 库本体
├── include
│ ├── myfunc1.h # 头文件
│ └── mufunc2.h # 头文件
└── README.md # 使用说明

4. 使用静态库

前提已经有:
  • libmylib.a(静态库)
  • xxx.h(头文件)
  • main.c(你的测试代码)

gcc main.c -L. -lmylib -o main

解释:
  • gcc main.c:编译测试文件
  • -L.:去当前目录找库文件
  • -lmylib:链接 libmylib.a;注意:库名是 libmylib.a,只写 -lmylib(自动补 lib 和 .a)
  • -o main:生成可执行文件 main.

在这里插入图片描述

问题:

为啥之前编译C语言的时候怎么不用这么复杂?还要加-L.和-l库名。

  • -l库名:(link library)-lmylib,告诉gcc要链接的库名称,而gcc默认就是链接C语言的库;不用加库名就认识;
  • -L库路径:(Library path)根据路径找库,告诉gcc去哪里找库,而gcc默认会去/lib64、/lib、/usr/local/lib下找。
  • 真正使用:

    1. 安装好库

    假设有一个自己的库:libmylib.a库文件和myfunc.h头文件。

    首先要将外部库文件放到/usr/local/lib/目录里;将外部头文件放到/usr/local/include/目录里。这就是安装好了库。注:不要放在/lib或/lib64或/usr/lib下,那是系统的,不要污染它,给我们提供了/usr/local/lib“安置房”就用它。

    • /usr/lib 是给【系统自带 /apt 安装】的官方库用的
    • /usr/local/lib 是给【自己写 / 自己编译】的私人库用的

    此时编译时就不用再带上寻找库的路径了,但还是要告诉编译器用的是哪个自己的库:

    gcc main.c -lmylib -o main

    2. 当 .h 文件不在当前目录,也不在 /usr/include 时,且没有安装好库

    main.c
    mylib/
    ├── lib
    │└── libmylib.a # 库本体
    ├── include
    │ ├── myfunc1.h # 头文件
    │ └── mufunc2.h # 头文件
    └── README.md # 使用说明

    此时编译时还需要告诉gcc去哪里找头文件:

    参数-I 头文件路径;(Include path)

    gcc main.c -I./include/ -L./mylib/lib -lmylib

    总结编译命令参数

    -l库名:只要是外部库就必须要带;

    -L 库路径:库文件**没有安装到/lib64/、/usr/lib、/usr/local/lib**目录下就要带;

    -I 头文件路径:头文件没有安装到/usr/include/目录下或者没有安装且头文件与main.c文件不在同目录下就必须带。

    三、动态库

    动态库的制作

    准备文件:

    add.c 函数实现
    sub.c
    myfunc.h 函数声明
    main.c 测试程序

    1. 编译

    将.c源文件编译成与位置无关的.o文件(必须加 -fPIC:Position Independent Code)

    gcc -c -fPIC add.c sub.c

    • -c:只编译,不链接
    • -fPIC:生成位置无关代码(必须要)

    2. 打包

    用的是**gcc命令打包**,而不是ar。把 .o 文件打包成动态库(必须加 -shared)

    gcc -shared *.o -o libmylib.so

    • -shared:生成动态库
    • 库名依然 必须以 lib 开头:libmylib.so

    3. 编译主程序main.c

    不管你用静态库 .a 还是动态库 .so

    编译主程序时:

    • -I 用法一样
    • -L 用法一样
    • -l 用法一样
    • 编译命令一模一样

    gcc main.c -I./include -L. -lmylib -o main

    注意:

    • 此时编译完之后与静态库不同,静态库main.c主程序编译完之后代码已经全焊死了,能直接运行;
    • 而动态库main.c主程序编译完之后,只是留了个地址,运行时再去找.so文件。

    4. 运行程序

    与静态库不同,用了动态库的可执行文件在运行时,还需要再告诉程序去哪里找库文件,跟编译时的寻找无关;

    Linux中,允许用户在环境变量中加一个变量:

    LD_LIBRARY_PATH:

    LD → Loader (程序加载器)
    LIBRARY → Library(库,专指动态库 .so)

    此变量的作用是,给动态库的程序一个查找动态库的路径。

    运行动态库程序时:

  • 先要找到动态库.so的位置;
  • 会先看有没有LD_LIBRARY_PATH,有的话就去这个路径下找;
  • 没有的话说明程序也就没有使用额外的动态库,再会去/lib、/usr/local/lib找动态库;
  • 问题:为啥不直接放进/usr/local/lib/目录中呢?这个目录又不是系统库,为啥不放?

    当然可以放!!!放了之后有多爽?就像编译主程序时不用参数-L、-I,而且在运行时还不需要配置LD_LIBRARY_PATH!!!

    但是:还需要一个步骤,更新动态库缓存表。

    动态库缓存表是Linux系统为提高查找动态库位置效率的表,因为我们添加了新的动态库,所以我们需要刷新这个表,不然程序在表里找不到新库。

    sudo ldconfig

    这个命令干两件事:

  • 扫描 /lib、/usr/lib、/usr/local/lib
  • 把找到的所有 .so 动态库更新到缓存表里
  • 3 个特点

  • 只对动态库有效,静态库 .a 完全不需要它
  • 只需要运行一次,放完库执行一次,永久生效
  • 必须加 sudo,因为要修改系统缓存
  • 赞(0)
    未经允许不得转载:171主机测评 » 自制 Linux 库文件:从编译.o 到部署.a/.so 的完整流程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址