一、Linux下静态库.o与.so库文件的生成与使用
创建文件目录,保存本次练习的文件

1、用 vi编辑器编辑生成所需要的 3 个文件

hello.h:
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
hello.c文件:
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
main1.c:
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
hello.h为该函数库的头文件。
hello.c是函数库的源程序,其中包含公用函数 hello,该函数将在屏幕上输出"Hello World!"。
main.c为测试库文件的主程序, 在主程序中调用了公用函数 hello。
2、将 hello.c 编译成.o 文件
无论静态库,还是动态库,都是由.o 文件创建的。因此,我们必须将源程序 hello.c 通过 g cc 先编译成.o 文件。
gcc -c hello.c //通过gcc命令将.c文件编译成.o文件

运行 ls 命令看看是否生成了 hello.o 文件(由运行结果可知已经生成了hello.o目标文件)
3、由.o 文件创建静态库,ls命令查看是否生成静态库
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。
ar -crv libmyhello.a hello.o //创建名为libmyhello.a的静态库文件

ls 命令结果中有 libmyhello.a,说明成功创建静态库。
4、在程序中使用静态库
静态库制作完了,那么如何使用它内部的函数呢?
只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。
注意,gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。
调用静态库文件的三种方法:
方法一:执行命令
gcc -o hello main1.c -L. –lmyhello

自定义的库时,main1.c 还可放在-L.和 –lmyhello 之间,但是不能放在它俩之后,否则会提示 myhello 没定义。
方法二:
gcc main1.c libmyhello.a -o hello

方法三:
先生成 main.o:
gcc -c main.c
再生成可执行文件:
gcc -o hello main.o libmyhello.a
然后运行./hello
./hello

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

程序照常运行,静态库中的公用函数已经连接到目标文件中了。
5、由.o 文件创建动态库文件。
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyh ello.so。
用 gcc 来创建动态库。 -o 必不可少
gcc -shared -fPIC -o libmyhello.so hello.o //创建动态库文件libmyhello.so

ls 命令结果中有 libmyhello.so,说明成功创建动态库。
6、在程序中使用动态库
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。
gcc -o hello main.c -L. -lmyhello //编译动态库文件
./hello //运行生成的可执行文件

此时系统报错,是因为找不到动态库文件 libmyhello.so。程序在运行时, 会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。只要将文件 libmyhello.so 复制到目录/usr/lib 中即可解决这个问题。
mv libmyhello.so /usr/lib //把libmyhello.so 复制到目录/usr/lib中

此时系统报错,原因是执行该项命令需要管理员权限。
sudo mv libmyhello.so /usr/lib //以管理员权限把libmyhello.so 复制到目录/usr/lib中
./hello

运行成功。
我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的 gcc 命令完全一样, 那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?抱着对问题必究到底的心情, 来试试看。
先删除hello文件中除.c 和.h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。
sudo rm -f hello hello.o /usr/lib/libmyhello.so //删除hello文件中除.c和.h的所有文件
ls

rm命令同样需要管理员权限,加上sudo。
再来创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so。
gcc -c hello.c //把.c文件编译成.o文件
ar -cr libmyhello.a hello.o //创建静态库文件 libmyhello.a
gcc -shared -fPIC -o libmyhello.so hello.o //创建动态库文件 libmyhello.so
ls

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

动态库和静态库同时存在时,优先使用动态库,当然,如果直接 gcc main.c libmyhello.a -o hello 的话,就是指定为静态库了
从程序 hello 运行的结果中很容易知道,当静态库和动态库同名时,gcc 命令将优先使用动态库,默认去连/usr/lib 和/lib 等目录中的动态库,将文件 libmyhello.so 复制到目录/usr/lib 中即可。
补充:实验用到的一些编译参数解析
-shared:该选项指定生成动态连接库(让连接器生成 T 类型的导出符号表,有时候也生成弱连接 W 类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L.:表示要连接的库在当前目录中;编译器默认在当前目录下先查找指定的库文件,如前面的“方法二 :gcc main.c libmyhello.a -o hello”)
-lmyhello:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上 lib,后面 加上.so 或.a 来确定库的名称 libmyhello.so 或 libmyhello.a。
二、静态库.a与.so库文件的生成与使用
先创建一个作业目录,保存本次练习的文件。
mkdir test2 //在当前文件夹建立名为tast2的目录
cd test2 //进入test2目录
1、用 vi编辑器编辑生成所需要的四个文件
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 文件的生成与使用
生成目标文件
gcc -c A1.c A2.c //生成相应的.o文件

生成静态库.a 文件
ar crv libafile.a A1.o A2.o //生成静态库.a 文件

使用.a 库文件,创建可执行程序(若采用此种方式,需保证生成的.a 文件与.c 文件保存在同一目录下,即都在当前目录下)
gcc -o test test.c libafile.a // 使用.a库文件,创建可执行程序
./test //运行程序

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

此时出现错误
运行 ldd test,查看链接情况
ldd test //查看test的链接情况

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

同时可直接使用 gcc -o test test.c -L. -lname,来使用相应库文件。
其中
-L.:表示在当前目录下,可自行定义路径 path,即使用-Lpath 即可。
-Lname:name:即对应库文件的名字(除开 lib),即若使用 libafile.a,则 name 为 afile; 若要使用 libsofile.so,则 name 为 sofile)。
三、程序实践
1、创建3个.c文件并写入内容,main1.h头函数定义
main1.h:
#ifndef HELLO_H
#define HELLO_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif //HELLO_H
sub1.c
#include"main1.h"
float x2x(int a,int b)
{
return a*b;
}
sub2.c
#include"main1.h"
float x2y(int a,int b)
{
return a/b;
}
main.c:
#include<stdio.h>
#include"sub1.h"
int main() //整型主函数main
{
int a=3,b=4; //定义a、b为整数型变量并赋值
printf("%f\n",x2x(a,b)); //以小数形式输出函数x2x的返回值
printf("%f\n",x2y(a,b)); //以小数形式输出函数x2y的返回值
return 0;
}
2、用gcc分别编译为3个.o 目标文件
gcc -c sub1.c sub2.c main1.c //把.c文件编译成.o目标文件
3、将目标文件生成1个 .a 静态库文件
ar crv libsub.a sub1.o sub2.o //将目标文件生成1个 .a 静态库文件

用 gcc将 main1函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小
gcc -o main1 main1.c libsub.a //将 main1函数的目标文件与此静态库文件进行链接,生成最终的可执行程序

用ls -la命令查看文件大小
ls -la //查看文件大小

此时可以看到静态库文件的大小为2688,main1的大小为8424。
4、将目标文件生成1个 .so 动态库文件
gcc -shared -fPIC -o libsub.so sub1.o sub2.o //生成动态库
gcc -o main2 main1.c libsub.so //生成可执行文件
sudo cp libsub.so /usr/lib //把 so 文件拷贝到对应路径
./main2 //运行文件
记得先把.so文件拷贝到/usr/lib下,否则会出现以下错误

然后运行文件

查看文件大小

此时动态库文件大小为7496,main2文件大小为8360。
5、结论
对比静态库和动态库,动态库文件比静态库文件要大得多,生成的可执行文件相差不大。
四、总结
一个源程序到一个可执行程序的过程:预编译、编译、汇编、链接。其中,编译是主要部分,其中又分为六个部分:词法分析、语法分析、语义分析、中间代码生成、目标代码生成和优化。
预编译:主要处理源代码文件中的以“#”开头的预编译指令。
编译:把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
汇编:将汇编代码转变成机器可以执行的指令(机器码文件)。
链接:“组装”模块的过程。把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确地衔接。
本文详细介绍了Linux环境下静态库.o与.so文件的生成、使用,包括静态库.a与.so的区别,通过实例演示了从源代码到可执行程序的全过程,以及静态库与动态库的选择和链接策略。

762

被折叠的 条评论
为什么被折叠?



