用 gcc 生成 .a 静态库和 .so动态库

目录

用 gcc 生成 .a 静态库和 .so 动态库

我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。

静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。

动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。

本文主要通过举例来说明在 Linux 中如何创建静态库和动态库,以及使用它们。(根据用 gcc 生成 .a 静态库和 .so 动态库使用手册逐步学习的)

在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o 文件。

一、 第 1 步:编辑生成例子程序 hello.h、hello.c 和 main.c。

先创建一个作业目录,保存本次练习的文件。

#mkdir test1
#cd test1
然后用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的 3 个文件。

hello.c是函数库的源程序,其中包含公用函数 hello,该函数将在屏幕上输出"Hello XXX!"。

hello.h为该函数库的头文件。

main.c为测试库文件的主程序,主程序中调用了公用函数 hello。

程序 1: hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
程序 2: hello.c
#include <stdio.h>
void hello(const char *name)
{
    printf("Hello %s!\n", name);
}
程序 3: main.c
#include "hello.h"
int main()
{
    hello("everyone");
    return 0;
}

二、第 2 步:将 hello.c 编译成.o 文件。

无论静态库,还是动态库,都是由.o 文件创建的。

因此,我们必须将源程序 hello.c 通过 gcc 先编译成.o 文件。在系统提示符下键入以下命令得到 hello.o 文件。

gcc -c hello.c

我们运行 ls 命令看看是否生存了 hello.o 文件。
在这里插入图片描述

在 ls 命令结果中,我们看到了 hello.o 文件,本步操作完成。
下面我们先来看看如何创建静态库,以及使用它。

三、第 3 步:由.o 文件创建静态库。

静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。

例如:我们将创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。

在创建和使用静态库时,需要注意这点。创建静态库用 ar 命令。
在系统提示符下键入以下命令将创建静态库文件libmyhello.a。

ar -crv libmyhello.a hello.o

我们同样运行 ls 命令查看结果:
在这里插入图片描述

ls 命令结果中有 libmyhello.a。

四、第 4 步:在程序中使用静态库。

静态库制作完了,如何使用它内部的函数呢?

只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。

注意,gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。

在main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用公用函数 hello。

下面先生成目标程序 hello,然后运行 hello 程序看看结果如何。

方法一:

gcc -o hello main.c -L. –lmyhello

使用自定义的库时,main.c 还可放在-L.和 –lmyhello 之间,但是不能放在它俩之后,否则会提示 myhello 没定义,但是是系统的库时,如 g++ -o main(-L/usr/lib) -lpthread main.cpp
就不出错。

在这里插入图片描述

方法二:

gcc main.c libmyhello.a -o hello

方法三:

先生成 main.o

gcc -c main.c

再生成可执行文件:

gcc -o hello main.o libmyhello.a

动态库连接时也可以这样做。

我们删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了。

rm libmyhello.a

./hello

程序照常运行,静态库中的公用函数已经连接到目标文件中了。
我们继续看看如何在 Linux 中创建动态库。我们还是从.o 文件开始。

五、第 5 步:由.o 文件创建动态库文件。

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyhello.so。用 gcc 来创建动态库。

在系统提示符下键入以下命令得到动态库文件 libmyhello.so。

gcc -shared -fPIC -o libmyhello.so hello.o

(-o 不可少)

我们照样使用 ls 命令看看动态库文件是否生成。
在这里插入图片描述

六、第 6 步:在程序中使用动态库;

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。

我们先运行 gcc 命令生成目标文件,再运行它看看结果。

gcc -o hello main.c -L. -lmyhello
(或 #gcc main.c libmyhello.so -o hello 不会出错(没有 libmyhello.so 的话,会出错),但是接下来./hello 会提示出错,因为虽然连接时用的是当前目录的动态库,但是运行时,是到/usr/lib 中找库文件的,将文件 libmyhello.so 复制到目录/usr/lib 中就 OK 了)

在这里插入图片描述

./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory

哦!出错了。快看看错误提示,原来是找不到动态库文件 libmyhello.so。程序在运行时,会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提
示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录/usr/lib 中,再试试。

成功了。这也进一步说明了动态库在程序运行时是需要的。
我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的 gcc 命令完全一样,
那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?抱着对问题必究到底的心情,
来试试看。
先删除除.c 和.h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。

rm -f hello hello.o /usr/lib/libmyhello.so

再来创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so。

gcc -c hello.c
ar -cr libmyhello.a hello.o (或-cvr )
gcc -shared -fPIC -o libmyhello.so hello.o

通过上述最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.s
o 都已经生成,并都在当前目录中。然后,我们运行 gcc 命令来使用函数库 myhello 生成目
标文件 hello,并运行程序 hello。

gcc -o hello main.c -L. –lmyhello

(动态库和静态库同时存在时,优先使用动态库,当然,如果直接
#gcc main.c libmyhello.a -o hello 的话,就是指定为静态库了)

./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory

从程序 hello 运行的结果中很容易知道,当静态库和动态库同名时,gcc 命令将优先使用动
态库,默认去连/usr/lib 和/lib 等目录中的动态库,将文件 libmyhello.so 复制到目录/usr/lib
中即可。

补充 1:编译参数解析

最主要的是 GCC 命令行的一个选项: -shared 该选项指定生成动态连接库(让连接器生成 T 类型的导出符号表,有时候也生成
弱连接 W 类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。

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

-L. 表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在源文件后面就可以了。比如:
gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop

其中-L/usr/lib 指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,

如前面的“法二 #gcc main.c libmyhello.a -o hello”)

-lmyhello 编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上 lib,后面加上.so 或.a 来确定库的名称 libmyhello.so 或 libmyhello.a。

LD_LIBRARY_PATH 这个环境变量指示动态连接器可以装载动态库的路径。

当然如果有 root 权限的话,可以修改/etc/ld.so.conf 文件,然后调用 /sbin/ldconfig 来达到同样的目的,不过如果没有 root 权限,那么只能采用输出 LD_LIBRARY_PATH 的方法了。

调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I”

include 进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过 ldd 命令察看时,就是死活找不到你指定链接的 so 文件,这时你要作的就是通过修改LD_LIBRARY_PATH 或者/etc/ld.so.conf 文件来指定动态库的目录。通常这样做就可以解决
库无法链接的问题了。

补充 2:

从上述可知,如何找到生成的动态库有 3 种方式:

(1)把库拷贝到/usr/lib 和/lib 目录下。

(2)在 LD_LIBRARY_PATH 环境变量中加上库所在路径。

例如动态库 libhello.so 在/home/example/lib 目录下:
e x p o r t L D L I B R A R Y P A T H = export LD_LIBRARY_PATH= exportLDLIBRARYPATH=LD_LIBRARY_PATH:/home/example/lib

(3) 修改/etc/ld.so.conf 文件,把库所在的路径加到文件末尾,并执行 ldconfig 刷新。

这样,加入的目录下的所有库文件都可见。

附:像下面这样指定路径去连接系统的静态库,会报错说要连接的库找不到:

g++ -o main main.cpp -L/usr/lib libpthread.a

必须这样

g++ -o main main.cpp -L/usr/lib -lpthread 才正确。

自定义的库考到/usr/lib 下时,

g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a

会出错,但是这样

g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass

就正确了。

七、编写程序实例

除了x2x函数之外,再扩展写一个x2y函数(功能自定),main函数代码将调用x2x和x2y ;将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件;将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小。

将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。

1.第一步:编写c语言文件

  • sub1.h
#ifndef __SUB1_H
#define __SUB1_H

#include<stdio.h>

float x2x(int a,int b);
float x2y(int c,int d);

#endif
  • sub1.c
#include "sub1.h"

float x2x(int a,int b)
{
    float ans;
    ans=a*b;
    return ans;
}
  • sub2.c
#include "sub1.h"

float x2y(int c,int d)
{
    float ans;
    ans=c-d;
    return ans;
}
  • main1.c
#include"sub1.h"

int main(){
    int a=9,b=10;
    printf("%.2f\n",x2x(a,b));
    printf("%.2f\n",x2y(a,b));    
    return 0;
}

2.第二步:用gcc分别编译为3个.o 目标文件;将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件,然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小。

编译:
在这里插入图片描述
在这里插入图片描述
用ar 生成一个静态库
在这里插入图片描述
用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序

在这里插入图片描述

3.第三步:将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。

在这里插入图片描述
在这里插入图片描述
可以看到静态库和动态库文件大小是一样的。

4.编写程序:Linux 下静态库.a 与.so 库文件的生成与使用

1.第一步:编写代码文件。
  • A1.c:
#include <stdio.h>
void print1(int arg){
printf("A1 print arg:%d\n",arg);
}
  • A2.c:
#include <stdio.h>
void print2(char *arg){
printf("A2 printf arg:%s\n", arg);
}
  • A.h
#ifndef A_H
#define A_H
void print1(int);
void print2(char *);
#endif
  • test.c:
#include <stdlib.h>
#include "A.h"
int main(){
print1(1);
print2("test");
exit(0);
}
2.第二步:静态库.a 文件的生成与使用。
1、生成目标文件(xxx.o)
gcc -c A1.c A2.c
1.2、生成静态库.a 文件
ar crv libafile.a A1.o A2.o
1.3、使用.a 库文件,创建可执行程序(若采用此种方式,需保证生成的.a 文件与.c 文件保

存在同一目录下,即都在当前目录下)

gcc -o test test.c libafile.a 
./test

在这里插入图片描述

3. 第三步:共享库.so 文件的生成与使用

2.1、生成目标文件(xxx.o)(此处生成.o 文件必须添加"-fpic"(小模式,代码少),否则在生成.so文件时会出错)
gcc -c -fpic A1.c A2.c
2.2 生成共享库.so 文件
gcc -shared *.o -o libsofile.so
2.3、使用.so 库文件,创建可执行程序
gcc -o test test.c libsofile.so
./test

发现出现错误:

./test: error while loading shared libraries: libsofile.so: cannot open shared object file: No
such file or directory

运行 ldd test,查看链接情况

ldd test
linux-vdso.so.1 => (0x00007fff0fd95000)
libsofile.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f937b5de000)
/lib64/ld-linux-x86-64.so.2 (0x0000563f7028c000)

在这里插入图片描述

发现确实是找不到对应的.so 文件。
这是由于 linux 自身系统设定的相应的设置的原因,即其只在/lib and /usr/lib 下搜索对应
的.so 文件,故需将对应 so 文件拷贝到对应路径。

sudo cp libsofile.so /usr/lib

再次执行./test,即可成功运行。

./test

在这里插入图片描述

同时可直接使用 gcc -o test test.c -L. -lname,来使用相应库文件
其中,
-L.:表示在当前目录下,可自行定义路径 path,即使用-Lpath 即可。
-lname:name:即对应库文件的名字(除开 lib),即若使用 libafile.a,则 name 为 afile;
若要使用 libsofile.so,则 name 为 sofile)。

八、Linux GCC常用命令

GCC 是编译工具,它的意思是 GNU C Compiler 。经过了这么多年的发展,GCC 已经不仅仅能支持 C 语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言等等。而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。
  Binutils是一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、 ldd、readelf、 size 等,主要作用如下表所示。
在这里插入图片描述

ELF文件

在这里插入图片描述

ELF文件的段

位于 ELF Header 和 Section Header Table 之间的都 是段(Section)。一个典型的 ELF 文件包含下面几个段:

  • text:已编译程序的指令代码段。
  • rodata:ro 代表 read only,即只读数据(譬如常数 const)。
  • data:已初始化的 C 程序全局变量和静态局部变量。
  • bss:未初始化的 C 程序全局变量和静态局部变量。
  • debug:调试符号表,调试器用此段的信息帮助调试。
反汇编ELF

由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包 含的指令和数据,需要使用反汇编的方法。
例如对一个hello.c文件进行反汇编:

  • 使用objdump -D对其进行反汇编
objdump -D hello
  • 使用objdump -S将其反汇编并将其C语言源代码混合显示出来
gcc -o hello -g hello.c //要加上-g 选项
objdump -S hello

Linux下的库文件分为两大类分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。

默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上-static选项,强制使用静态链接库。

在/usr/dev/mysql/lib目录下有链接时所需要的库文件libmysqlclient.so和libmysqlclient.a,为了让GCC在链接时只用到静态链接库,可以使用下面的命令:

gcc –L /usr/dev/mysql/lib –static –lmysqlclient test.o –o test
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值