编写自己的链接库——西邮本科生的实验
扫描二维码
随时随地手机看文章
作者:刘传玺 、盛洁、黄子轩
一. 实验目的及实验环境
1.实验目的
通过编译和链接一个程序,深入理编译和链接都做了什么,并掌握静态库和动态链接库的编写和调用方法。
2.实验环境
(1)硬件
CPU:
内存:
显示器:1920*1080 60Hz
硬盘空间: 40GB
(2)软件
虚拟机名称及版本:VMware
操作系统名称及版本:Ubuntu16.04
编译器:gcc
二. 实验内容
1、实验前准备工作
1)阅读参考资料,了解编译链接的过程
C/C++语言编写的程序转换成为处理器能够执行的二进制代码的过程,包括四个 步骤:
预处理(Preprocessing)
编译(Compilation)
汇编(Assembly)
链接(Linking)
其中编译就是把预处理之后的文件进行一系列词法分析、语法分析、语义分析以及优化后生成的相应汇编代码文件。汇编就是将编译后的汇编代码翻译为机器码,几乎每一条汇编指令对应一句机器码。链接是将汇编产生的目标文件和所使用的库函数的目标文件链接生成一个可执行文件的过程。
2)学习gcc、size、ar、ldd、readelf、nm等命令的使用
gcc 命令
size 命令
size 命令基本上就是输出指定输入文件各段及其总和的大小。
命令 |
|
size 目标文件/可执行文件名 | 输出文本段、数据段和 bss 段及其相应的大小。然后是十进制格式和十六进制格式的总大小。最后是文件名。 |
size 目标文件/可执行文件名 --format=SysV | 切换输出格式 |
size 目标文件/可执行文件名 -d | 各个段的大小以十进制数字的格式显示 |
size 目标文件/可执行文件名 -o | 各个段的大小以八进制数字的格式显示 |
size 目标文件/可执行文件名 -x |
各个段的大小以十六进制数字的格式显示 |
size -t [file1] [file2] ... | 如果用 size 一次性查找多个文件的段大小,则通过使用 -t 选项还可 以让它显示各列值的总和。 |
size --common [file1] [file2] ... | 输出每个文件中公共符号的总大小 |
ar 命令
用于建立或修改备存文件,或是从备存文件中抽取文件。
语法:ar[-dmpqrtx][cfosSuvV][a<成员文件>][备存文件][成员文件]
下表是常见命令选项
ldd 命令
可以查看一个可执行程序依赖的共享库
下表是常见命令选项
readelf 命令
用于显示读取 ELF 文件中信息
格式:readelf <option(s)> elf-file(s)
下表是常见命令选项
nm 命令
可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。
对目标文件和可执行文件而言, 可以获得其中的函数;
另外可以从静态库和动态库中获取到函数名称。
3)把原来写好的生产者-消费者问题的代码准备好。
2、实验要求
从生产者-消费者,读者-写者,哲学家就餐问题的中选择一个自己感兴趣的 代码对其进行改造,将其拆分成 2 个以上的文件单独编译。
1)通过 ar 命令将其打包成静态库,并调用自己的静态库编写程序运行, 查看结果,并用 size 命令查看各个段的大小。
2)通过 gcc 产生动态链接库,并运行,用 ldd 命令查看文件的依赖。
3、提问并回答
在讨论区提出至少两个问题,并给予回答,或同组内,两个同学为一组,一 个提问,一个回答。
三.方案设计
1.给出静态库生成的过程方案。
我使用的是生产者消费者的代码,我将宏定义和函数放到 lcx.h 这个头文件中,再将函数定义和全局变量放到 lcx.c 这个文件中,主函数放到 main.c 这个文件中作为程序入口。之后,步骤如下:
第一步:编辑源文件,lcx.h,lcx.c,main.c。其中 main.c 文件中包含 main 函 数,作为程序入口;main.c 中包含 main 函数中需要用到的函数。
第二步:将 main.c 编译成目标文件。gcc -c lcx.c,得到 lcx.o 这个目标文件。
第三步:由.o 文件创建静态库。ar -rcs lcx.a lcx.o 创建完成后可以使用nm 查看 lcx.a 中的内容。使用 ar -t lcx.a 也可以
第四步:在程序中使用静态库。gcc main.c -L. -l lcx.a -lpthread -L 指出链接的库在当前目录下,-l 加链接库的名字 因为是静态编译,生成的执行文件可以独立于.a 文件运行。
第五步:执行。
2. 给出动态库生成的过程
第一步:编辑源文件,与创建静态库相同,代码无变化
第二步:由 lcx.c 文件创建动态库文件。
gcc -fPIC -shared -o lcx.so lcx.c
这里一定要用-o 重命名选项,不然默认输出文件为 a.out 与编译出的可执行文件重名,到时候编译出来的可执行文件会覆盖掉动态库。
这里也可以使用 nm lcx.so 来查看动态库中的内容
第三步:在程序中使用动态库。gcc main.c -L -l ./lcx.so -lpthread
这里动态库的路径最好使用绝对路径或相对路径,如果只写文件名容易报 错。
第四步:执行。
四.总结
1.实验过程中遇到的问题及解决办法
问题:所遇到的问题请见 error 截图,是编译过程的错误。
解决办法:利用 vimf1.c 进行代码的查验,发现没有错误,于是考虑到可能是由于子代码的错误导致进行静态库创建出现了问题,于是对 test.c 进行查验,发现 void* producer 函数头没有进行编写,而是将其内容作为 print()函数的功能,另外补充了 print()函数后成功编译。
2.对设计及调试过程的心得体会
心得体会:在这次编译代码利用静态库和动态库的过程中我学到了很多知识,静态库或静态链接库是一组例程,外部函数和变量,它们在编译时在调用者中解析,并由编译器、链接器或绑定器复制到目标应用程序中,从而生成目标文件和一个独立的可 执行文件。动态链接只包括库的地址(而静态链接是浪费空间)动态链接在运行时链接库。
静态库虽然可以在多个程序中重用,但在编译时会被锁定到程序中。另一方面, 动态或共享库作为可执行文件之外的单独文件存在。接下来我分析动态库静态库的优缺点,使用静态库的缺点是它的代码被锁定到最终的可执行文件中,如果没有重新编译就无法修改。相反,可以修改动态库而无 需重新编译。
由于动态库位于可执行文件之外,因此程序只需在编译时制作库文件的一个 副本。而使用静态库意味着程序中的每个文件都必须在编译时拥有它自己的库文件副本。
使用动态库的缺点是程序更容易破坏。例如,如果动态库损坏,则可执行文 件可能不再起作用。但是,静态库是不可触及的,因为它存在于可执行文件中。
使用动态库的好处是,多个正在运行的应用程序可以使用相同的库,而无需 每个应用程序拥有自己的副本。
最后分析就可以得出它们的适用范围,如果你有很多文件,静态库的多个副 本意味着可执行文件的大小增加,那就建议使用动态库,可以节省时间。如果执行时间的好处超过节省空间的需要,那么静态库就是最佳选择。
五.附录:源代码
main.c
//pv操作:生产者与消费者经典问题 //author:leaf
extern int in; /*生产者放置产品的位置*/
extern int out; /*消费者取产品的位置*/
extern int buff[M]; /*缓冲初始化为0, 开始时没有产品*/
extern sem_t sem_dr; /*同步信号量,当满了时阻止生产者放产品*/
extern sem_t sem_co; /*同步信号量,当没产品时阻止消费者消费*/
extern pthread_mutex_t mutex; /*互斥信号量, 一次只有一个线程访问缓冲*/
int main()
{
pthread_t id1;
pthread_t id2;
pthread_t id3;
pthread_t id4;
int i;
int ret;
sem_mutex_init();
/*create the producer thread*/
ret = pthread_create(&id1, NULL , producer, NULL );
if (ret != 0)
{
printf ("producer creation failed \n");
exit (1);
}
ret = pthread_create(&id3, NULL , producer, NULL );
if (ret != 0)
{
printf ("producer creation failed \n");
exit (1);
}
/*create the consumer thread*/
ret = pthread_create(&id2, NULL , consumer, NULL );
if (ret != 0)
{
printf ("consumer creation failed \n");
exit (1);
}
ret = pthread_create(&id4, NULL , consumer, NULL );
if (ret != 0)
{
printf ("consumer creation failed \n");
exit (1);
}
pthread_join(id1, NULL );
pthread_join(id2, NULL );
pthread_join(id3, NULL );
pthread_join(id4, NULL );
exit (0);
}
lcx.h
#ifndef LCX_H #define LCX_h
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define M 32 /*缓冲数目*/
#define P( x ) sem_wait(& x )
#define V( x ) sem_post(& x )
void print();
void* producer();
void* consumer();
void sem_mutex_init();
#endif
lcx.c
int in = 0; /*生产者放置产品的位置*/
int out = 0; /*消费者取产品的位置*/
int buff[M] = { 0 }; /*缓冲初始化为0, 开始时没有产品*/
sem_t sem_dr; /*同步信号量,当满了时阻止生产者放产品*/
sem_t sem_co; /*同步信号量,当没产品时阻止消费者消费*/
pthread_mutex_t mutex; /*互斥信号量, 一次只有一个线程访问缓冲*/
/*
*output the buffer
*/
void print()
{
int i;
for (i = 0; i < M; i++)
printf ("%d ", buff[i]);
printf ("\n");
}
/*
*producer
*/
void* producer()
{
for (;;)
{
sleep(1);
P(sem_dr);
pthread_mutex_lock(&mutex);
in = in % M;
printf ("(+)produce a product. buffer:");
buff[in] = 1;
print();
++in;
pthread_mutex_unlock(&mutex);
V (sem_co);
}
}
/*
*consumer
*/
void* consumer()
{
for (;;)
{
sleep(2);
P(sem_co);
pthread_mutex_lock(&mutex);
out = out % M;
printf ("(-)consume a product. buffer:");
buff[out] = 0;
print();
++out;
pthread_mutex_unlock(&mutex);
V (sem_dr);
}
}
void sem_mutex_init()
{
/*
*semaphore initialize
*/
int init1 = sem_init(&sem_dr, 0, M);
int init2 = sem_init(&sem_co, 0, 0);
if ((init1 != 0) && (init2 != 0))
{
printf ("sem init failed \n");
exit (1);
}
/*
*mutex initialize
*/
int init3 = pthread_mutex_init(&mutex, NULL );
if (init3 != 0)
{
printf ("mutex init failed \n");
exit (1);
}
}
猜你喜欢:
【Linux笔记】pc机_开发板_ubuntu互ping实验
【Linux笔记】挂载网络文件系统
后台回复:加群。添加ZhengN微信,加入交流群
点个赞,证明你还爱我
免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!