赞
踩
目录
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
站在库的制作者的角度,如果我想写一个库文件,肯定是不能写main函数的,并且要将所有的方法打包好给用户。
比如我们现在创建好了四个.h头文件进行声明,还有四个.c文件进行定义(这四个文件只有加减乘除四个函数)。
我们写一个主函数include一下这四个头文件
gcc编译一下然后执行,没啥问题
但是一般情况下,对于多文件的编译,我们不会直接gcc,因为这样每一次编译,所有的文件都要进行预处理、编译、汇编、链接。一般建议先把源文件编译成 .o 文件。最后将.o文件进行链接形成可执行文件。
我们写一个makefile来帮助编译。其中 %. 是省略写法,只要后缀相同,前面的会自动遍历,$<也是省略写法,会将依赖文件一个个展开。
这样确实可以,但依然不够优雅,我想要讲所有的 .o 文件打包成一个文件
使用以下命令将.o文件打包成静态库
ar -rc libmymath.a Add.o Div.o Mul.o Sub.o
那么现在有了这个静态库文件,我们应该如何使用呢?
我们模拟一下真实情况,目前只有.h文件(因为要include,因此.h是必须的)、静态库文件、还有我们写的主函数。
使用以下命令即可链接静态库编译成可执行文件
gcc main.c -lmymath -L.
gcc默认动态链接,但个别库只提供了静态库,gcc只能局部性的用我们指定的静态库进行静态链接,其他库正常动态链接。如果-static,就必须要全部使用静态库。
但是这样还是有点戳,说好的打包,你是打包了,但一看这么全,把库文件和.h文件都放在一起,很难看的,我想将.h放在一个文件夹中,库文件放在一个文件夹中。
使用Makefile来帮我们部署。
现在形成了mymath_lib文件夹,现在就打包好了
以后我们要发送给其他用户,就将文件夹压缩发给其他用户使用即可。
其他用户收到了这个包,先创建c文件
系统搜索头文件默认是在/usr/include文件夹中,那么我们将新下载的库的头文件,拷贝到该文件夹中,这个行为就叫做安装。
库文件默认在/lib64文件夹中,依然是将库文件拷贝到该文件夹中,后续直接gcc 编译程序就可以了。
文件中include指定相关目录即可在编译时找到库的头文件
再使用如下代码即可编译可执行文件,-I(大写i)后接头文件目录,代表需要去这里找头文件,-l(小写L)后接库名,-L后接库目录,代表这里找库文件。
gcc main.c -I mymath_lib/include -lmymath -L mymath_lib/lib
依然是将.h文件和.c文件先放在一起,并写一个Makefile帮我们部署。
这里形成动态库的命令如下,是使用的gcc(gcc内置了动态链接,证明动态库更重要一点)shared: 表示生成共享库格式
gcc -shared -o libmymath.so Add.o Div.o Mul.o Sub.o
后面的 fPIC 为位置无关码,后续我们会讲到。
现在我们make编译后,再make output进行打包,就有一个mymath_lib这个打包好的动态库文件夹
里面存放的文件如下
用户收到别人写的动态库,再写上自己的主程序,就可以开始编译运行了
使用如下代码编译,编译上跟静态库没有区别
gcc main.c -I mymath_lib/include/ -lmymath -L mymath_lib/lib/
但是我们执行的时候报错了,他找不到libmymaty.so文件
这是因为可执行程序和静态库被打包在一起,编译成可执行程序,便和静态库没关系了,因此运行时直接运行就可以了。但动态库和可执行程序是分离的,并不是同一文件,在运行时,可执行程序和动态库都要加载到内存中,虽然我们之前告诉编译器,库文件在哪里,但是执行程序跟编译是两码事,因此还是找不到。
拷贝头文件
拷贝库
之后执行a.out就没问题了
并且,由于我们库文件已经安装了,后面编译也可以简单一点,告诉编译器我们使用了哪个库文件就好了,不需要在 -I(include路径) -L(lib路径)
gcc main.c -lmymath
同时可以使用 ldd a.out 查看可执行文件依赖了那些动态库。
使用如下代码进行软链接
ln -s mymath_lib/lib/libmymath.so libmymath.so
这句代码就是将文件夹中的libmymath.so软链接到当前目录下,因此当前路径就多了一个libmymath.so
执行可执行程序时,虽然他不会进入当前目录的文件夹里面找,但是会在当前目录下查找是否有动态库,因此我们软链接到当前目录下,就可以执行了。
当然,我们也可以将mymath库的软链接安装到系统中,也是可以的。
ln -s ~/centos_test/109/240314_lib/dytest/test/mymath_lib/lib/libmymath.so /lib64/libmymath.so
Linux下有一个环境变量名为 LD_LIBRARY_PATH 。系统还会去该环境变量中搜索动静态库。因此我们将目录添加到该环境变量中,就可以了,代码如下
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/centos_test/109/240314_lib/dytest/test/mymath_lib/lib/
系统中有一个目录 /etc/ld.so.conf.d/ ,ld(加载),so(动态库后缀),conf(配置文件),d(目录)
这些系统文件管理者动态库的加载,配置文件中只需要写一个路径就可以了
再sudo ldconfig 进行刷新,就能找到了
如果别人给我的库文件,同时包含静态库和动态库,那么我们编译时默认使用的是动态库,要使用静态库依然需要加上-static。
我们学习了动静态库的制作和使用,现在我们要来实战一下,安装ncurses库。
sudo yum install ncurses-devel.x86_64
有了之前的理解,我们知道安装库的本质就是将头文件拷贝到/usr/include 文件夹下,将库文件拷贝到 /lib64/ 文件夹下。
下载好我们创建ncurses.c文件,并写一个贪吃蛇代码测试一下。
- #include <ncurses.h>
- #include <stdlib.h>
- #include <time.h>
- #include <unistd.h>
-
- #define DELAY 100000
-
- int main() {
- int x, y, maxX, maxY; //蛇头的位置和终端窗口的大小
- int direction = KEY_RIGHT; //方向
- int snakeLength = 5; //蛇的长度
- int snakeX[100], snakeY[100]; //蛇身的位置
- int foodX, foodY; //食物的位置
- int score = 0; //得分
- int gameOver = 0; //游戏结束标志
-
- // 初始化ncurses库
- initscr();
- noecho();
- curs_set(0);
- keypad(stdscr, TRUE);
- timeout(0);
-
- // 获取终端窗口的大小
- getmaxyx(stdscr, maxY, maxX);
-
- // 初始化蛇的初始位置和长度
- x = maxX / 2;
- y = maxY / 2;
- for (int i = 0; i < snakeLength; i++) {
- snakeX[i] = x - i;
- snakeY[i] = y;
- }
-
- // 生成食物的初始位置
- srand(time(NULL));
- foodX = rand() % maxX;
- foodY = rand() % maxY;
-
- // 游戏循环
- while (!gameOver) {
- clear();
-
- // 绘制蛇
- for (int i = 0; i < snakeLength; i++) {
- mvprintw(snakeY[i], snakeX[i], "O");
- }
-
- // 绘制食物
- mvprintw(foodY, foodX, "*");
-
- // 显示分数
- mvprintw(0, 0, "Score: %d", score);
-
- // 移动蛇的位置
- int nextX = snakeX[0];
- int nextY = snakeY[0];
- switch (direction) {
- case KEY_UP:
- nextY--;
- break;
- case KEY_DOWN:
- nextY++;
- break;
- case KEY_LEFT:
- nextX--;
- break;
- case KEY_RIGHT:
- nextX++;
- break;
- }
-
- // 检查是否吃到食物
- if (nextX == foodX && nextY == foodY) {
- score++;
- snakeLength++;
- foodX = rand() % maxX;
- foodY = rand() % maxY;
- }
-
- // 移动蛇的身体
- for (int i = snakeLength - 1; i > 0; i--) { //后一节移动到前一节的位置
- snakeX[i] = snakeX[i - 1];
- snakeY[i] = snakeY[i - 1];
- }
-
-
- // 更新蛇头位置
- snakeX[0] = nextX;
- snakeY[0] = nextY;
-
- // 检查游戏结束条件
- //检查是否越界
- if (nextX < 0 || nextX >= maxX || nextY < 0 || nextY >= maxY) {
- gameOver = 1;
- }
- //检查是否撞到自己的身体
- for (int i = 1; i < snakeLength; i++) {
- if (snakeX[i] == nextX && snakeY[i] == nextY) {
- gameOver = 1;
- }
- }
-
- // 刷新屏幕
- refresh();
-
- // 延迟一段时间
- usleep(DELAY);
-
- // 获取用户输入
- int key = getch();
- switch (key) {
- case KEY_UP:
- case KEY_DOWN:
- case KEY_LEFT:
- case KEY_RIGHT:
- direction = key;
- break;
- case 'q':
- gameOver = 1;
- break;
- }
- }
-
- // 清理并退出ncurses库
- endwin();
-
- printf("Game Over! Your score: %d\n", score);
-
- return 0;
- }

使用c99标准编译成test可执行文件,注意一定要加 -lncurses 代表你使用了这个库
gcc nucurses.c -o test -lncurses -std=c99
./test 执行一下,代码就跑起来了
妈妈再也不用担心别人给我的静态库或者动态库不会使用啦。
之前我们动态库编译的时候 代码为 gcc -fPIC -c XXX.c
其中fPIC为 与位置无关码 。
- 首先我们需要清楚需要动态链接的可执行程序,不光会将他自己的代码加载到内存,还需要将链接的库也加载到内存,这样才可以找得到。
- 在程序还没有被加载时,程序内部也是有地址的,C语言的变量名和函数名,在编译为2进制后,就没有了名字的概念,取而代之的是地址。我们代码中gcc -c 形成的.o文件就是二进3.制文件,他只有地址的说法,没有变量的说法。
- 那么我们在编译的时候,就需要对代码进行编址,这仍然遵循虚拟地址空间那一套。他不仅仅是操作系统的概念,在编译器编译的时候也要按照这样的规则编译可执行程序。这样在加载的时候,就很好进行数据的对应。
- 编址的时候,可以进行绝对编址和相对编址,绝对编址就是实实在在的是什么地址就是什么地址,每一次加载的地址都可能不一样,相对编址就是某一函数,相对于程序入口地址是多少,那么我们可以保证在代码不变的情况下,使用相对编址,他里面的函数或变量的相对地址也不会发生改变。这就叫与位置无关码。
我们知道静态库在链接时,会链接上我的代码,他们打包形成了一个可执行程序,因此运行时数据都在可执行程序中,不需要去外部找内容,所以我们执行静态库链接的程序,直接运行就好了。
而动态库链接运行时,我们的代码需要加载到内存,我们代码使用到的库也需要加载到内存,这样才可以找到相关内容,成功运行。动态库被加载之后,映射到了我写的程序task_struct中的指针所指向的虚拟地址空间中的共享区部分。
动态库可以在共享区的任何位置。那我们的代码在运行时需要去找到动态库文件,那就可以直接去共享区查找,再通过页表映射,找到库文件所在的物理内存。当其他的可执行程序,也需要使用到该动态库的时候,也是在他的进程地址空间中的共享区中,用他的页表映射,找到物理内存中的库。我们的意思是动态库在物理内存中只有一份,而在不同的进程的虚拟内存空间中,可以有N多份,只要建立好页表映射就可以了!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。