【Linux系统编程】第三十一弹---深入理解静态库:从零开始制作与高效使用的完全指南

news/2024/10/18 17:24:52/

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】

目录

1、静态库

1.1、怎么做静态库

1.2、怎么使用静态库


1、静态库

1.1、怎么做静态库

在Linux环境下,通常使用GCC(GNU Compiler Collection)编译器来编译源代码,并使用ar(archiver)工具来创建静态库。

  1. 编写源代码:首先,你需要有一些源代码文件,比如 x.c ,y.c ,z.c

  2. 编译源代码为对象文件:使用GCC编译器将源代码编译为目标文件(.o文件)

  3. 创建静态库:使用 ar工具将对象文件打包成静态库

头文件是一个手册提供函数的声明,告诉用户怎么用;.o提供实现,我们只需要补上一个main函数,调用头文件提供的方法,然后和.o进行链接,就能形成可执行

mymath.h

#pragma once // 防止头文件重复包含#include <stdio.h>int Add(int x,int y);

mymath.c

#include "mymath.h"int Add(int x,int y)
{return x + y;
}

mystdio.h

#pragma once #include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define LINE_SIZE 1024
#define FLUSH_NOW  1
#define FLUSH_LINE 2
#define FLUSH_FULL 4typedef struct _myFILE  
{unsigned int flags;int fileno;// 缓冲区char cache[LINE_SIZE];int cap;// 容量int pos;// 下次写入的位置
}myFILE;myFILE* my_fopen(const char* path,const char* flag);
void my_fflush(myFILE* fp);
ssize_t my_fwrite(myFILE* fp,const char* data,int len);
void my_fclose(myFILE* fp);

mystdio.c

#include "mystdio.h"myFILE* my_fopen(const char* path,const char* flag)
{int flag1 = 0;int iscreate = 0;mode_t mode = 0666;if(strcmp(flag,"r") == 0){flag1 = O_RDONLY;}else if(strcmp(flag,"w") == 0){flag1 = (O_WRONLY | O_CREAT | O_TRUNC);iscreate = 1;}else if(strcmp(flag,"a") == 0){flag1 = (O_WRONLY | O_CREAT | O_APPEND);iscreate = 1;}else {}int fd = 0;if(iscreate)fd = open(path,flag1,mode);else fd = open(path,flag1);if(fd < 0) return NULL;myFILE* fp = (myFILE*)malloc(sizeof(myFILE));if(fp == NULL) return NULL;fp->fileno = fd;fp->flags = FLUSH_LINE;fp->cap = LINE_SIZE;fp->pos = 0;return fp;
}
void my_fflush(myFILE* fp)
{write(fp->fileno,fp->cache,fp->pos);fp->pos = 0;
}
ssize_t my_fwrite(myFILE* fp,const char* data,int len)
{// 写入的本质是拷贝,条件允许就刷新memcpy(fp->cache + fp->pos ,data,len);// 考虑扩容与越界问题fp->pos += len;if((fp->flags&FLUSH_LINE) && fp->cache[fp->pos-1] == '\n'){my_fflush(fp);}return len; 
}
void my_fclose(myFILE* fp)
{my_fflush(fp);close(fp->fileno);free(fp);
}

main.c

#include "mymath.h"
#include "mystdio.h"
#include <string.h>
#include <stdio.h>int main()
{int a = 10;int b = 20;printf("%d + %d  = %d\n",a,b,myAdd(a,b));myFILE* fp = my_fopen("log.txt","w");if(fp == NULL) return 1;const char* message = "这是我写的...\n";my_fwrite(fp,message,strlen(message));my_fclose(fp);return 0;
}

编译并执行程序

[jkl@host lib]$ gcc main.c mymath.c mystdio.c
[jkl@host lib]$ ls
a.out  log.txt  main.c  mymath.c  mymath.h  mystdio.c  mystdio.h  
[jkl@host lib]$ ./a.out
10 + 20  = 30
[jkl@host lib]$ cat log.txt
这是我写的...

将.c文件(源文件)编译成.o文件(目标文件) [ -c选项告诉GCC只编译和汇编,但不链接]

gcc -c mymath.c  # 将mymath.c文件编译成.o文件,默认编译成mymath.o
gcc -c mystdio.c # 将mystdio.c文件编译成.o文件,默认编译成mystdio.o

 ​​​​​​

使用.h 文件和.o 文件编译main.c程序

[jkl@host roommate]$ ls
main.c  mymath.h  mymath.o  mystdio.h  mystdio.o
[jkl@host roommate]$ gcc main.c
/tmp/ccFk2rMI.o: In function `main':
main.c:(.text+0x21): undefined reference to `myAdd'
main.c:(.text+0x49): undefined reference to `my_fopen'
main.c:(.text+0x84): undefined reference to `my_fwrite'
main.c:(.text+0x90): undefined reference to `my_fclose'
collect2: error: ld returned 1 exit status

 gcc main.c 只编译了main.c文件,并没有包含对mymath.o 和 mystdio.h 的链接操作,因为main.c 依赖于mymath.h 和 mystdio.h 中声明的函数,因此仅编译main.c是不够的。

解决办法一:

gcc main.c mymath.o mystdio.o -o myexe

将.o文件 和.c文件一起编译链接。 

解决办法二:

将main.o也编译成.o文件

[jkl@host roommate]$ gcc -c main.c
[jkl@host roommate]$ ls
main.c  main.o  mymath.h  mymath.o  mystdio.h  mystdio.o
[jkl@host roommate]$ gcc mymath.o mystdio.o main.o -o myexe
[jkl@host roommate]$ ls
main.c  main.o  myexe  mymath.h  mymath.o  mystdio.h  mystdio.o
[jkl@host roommate]$ ./myexe
10 + 20  = 30
[jkl@host roommate]$ ls
log.txt  main.c  main.o  myexe  mymath.h  mymath.o  mystdio.h  mystdio.o
[jkl@host roommate]$ cat log.txt
这是我写的...

通过ar指令将所有.o文件打包: 

ar -rc libmyc.a *.o # 将所有.o文件打包成libmyc.a文件

r(replace) 选项表示替换库中已存在的文件

c(create) 选项表示创建一个新的库 。

1.2、怎么使用静态库

  • 方式一:直接使用打包的文件

为了更好的使用静态库,我们把前面打包的文件拷贝到另外的目录进行操作。

cp libmyc.a roommate/ # 将打包的文件拷贝到下级目录下
[jkl@host roommate]$ cp ../mymath.h . # 将.h文件拷贝到下级目录
[jkl@host roommate]$ cp ../mystdio.h .
[jkl@host roommate]$ ls
main.c  myexe  mymath.h  mymath.o  mystdio.h  mystdio.o

 直接使用gcc 编译

[jkl@host roommate]$ gcc main.c
/tmp/ccuZcLr1.o: In function `main':
main.c:(.text+0x21): undefined reference to `myAdd'
main.c:(.text+0x49): undefined reference to `my_fopen'
main.c:(.text+0x84): undefined reference to `my_fwrite'
main.c:(.text+0x90): undefined reference to `my_fclose'
collect2: error: ld returned 1 exit status

使用gcc 编译main.c 和 libmyc.a 

[jkl@host roommate]$ gcc main.c libmyc.a
[jkl@host roommate]$ ls
a.out  libmyc.a  main.c  mymath.h  mystdio.h
[jkl@host roommate]$ ./a.out
10 + 20  = 30
[jkl@host roommate]$ ls
a.out  libmyc.a  log.txt  main.c  mymath.h  mystdio.h
[jkl@host roommate]$ cat log.txt
这是我写的...
  •  方式二:将打包的文件拷贝到系统库中(严重不推荐)

我们可以将自己写的.h头文件写到/usr/bin/目录下。

我们可以将自己打包的方法实现文件写到/usr/bin54/目录下。 

 

查看拷贝的文件 

[jkl@host roommate]$ ls /usr/include/mymath.h
/usr/include/mymath.h
[jkl@host roommate]$ ls /usr/include/mystdio.h
/usr/include/mystdio.h
[jkl@host roommate]$ ls /usr/lib64/libmyc.a
/usr/lib64/libmyc.a

把上面的两步操作做完之后,我们可以直接编译main函数,头文件可以使用<>

main.c

#include <mymath.h>
#include <mystdio.h>
#include <stdio.h>
#include <string.h>int main()
{int a = 10;int b = 20;printf("%d + %d  = %d\n",a,b,myAdd(a,b));myFILE* fp = my_fopen("log.txt","w");if(fp == NULL) return 1;const char* message = "这是我写的...\n";my_fwrite(fp,message,strlen(message));my_fclose(fp);return 0;
}

直接使用gcc编译还是会报错,因为该方法的实现是我们自己写的,gcc/g++不认识,所以直接编译会报错。 

[jkl@host roommate]$ gcc main.c
/tmp/ccZqyRSO.o: In function `main':
main.c:(.text+0x21): undefined reference to `myAdd'
main.c:(.text+0x49): undefined reference to `my_fopen'
main.c:(.text+0x84): undefined reference to `my_fwrite'
main.c:(.text+0x90): undefined reference to `my_fclose'
collect2: error: ld returned 1 exit status

在gcc编译.c文件之后需要加参数,-l libmyc.a,且需要去掉lib和.a,因此正确的命令是gcc main.c -lmyc (-l后面可以加空格也可以不加空格) 

[jkl@host roommate]$ gcc main.c -lmyc
[jkl@host roommate]$ ls
a.out  main.c  mylib
[jkl@host roommate]$ ./a.out
10 + 20  = 30
[jkl@host roommate]$ ls
a.out  log.txt  main.c  mylib
[jkl@host roommate]$ cat log.txt
这是我写的...

 第二种方式不推荐,因此演示完之后最好将拷贝的文件给删除掉。

[jkl@host roommate]$ sudo rm /usr/include/mymath.h
[jkl@host roommate]$ sudo rm /usr/include/mystdio.h
[jkl@host roommate]$ sudo rm /usr/lib64/libmyc.a
[jkl@host roommate]$ ls /usr/include/mymath.h
ls: cannot access /usr/include/mymath.h: No such file or directory
[jkl@host roommate]$ ls /usr/include/mystdio.h
ls: cannot access /usr/include/mystdio.h: No such file or directory
[jkl@host roommate]$ ls /usr/lib64/libmyc.a
ls: cannot access /usr/lib64/libmyc.a: No such file or directory

 方式三:通过命令链接静态库

[jkl@host roommate]$ tree .
.
|-- main.c
`-- mylib|-- include|   |-- mymath.h|   `-- mystdio.h`-- lib`-- libmyc.a3 directories, 4 files

为什么不能直接使用 gcc main.c myc.a?

 因为告诉了gcc/g++编译器,但是没有告诉操作系统!!!

使用静态库:在编译其他程序时,可以通过-I(指定用户自定义头文件搜索路径)  -L(指定用户自定义库文件搜索路径)和 -l(执行确定的第三方库名称,去掉前缀lib和后缀.a)选项来链接静态库

[jkl@host roommate]$ gcc main.c -I ./mylib/include/ -L ./mylib/lib -lmyc
[jkl@host roommate]$ ls
a.out  main.c  mylib
[jkl@host roommate]$ ./a.out
10 + 20  = 30
[jkl@host roommate]$ ls
a.out  log.txt  main.c  mylib
[jkl@host roommate]$ cat log.txt
这是我写的...

上面是动态链接的

[jkl@host roommate]$ ldd a.outlinux-vdso.so.1 =>  (0x00007ffef6bf9000)libc.so.6 => /lib64/libc.so.6 (0x00007f0448055000)/lib64/ld-linux-x86-64.so.2 (0x00007f0448423000)

 gcc在不使用static选项的时候,并且只提供.a,只能静态链接当前的.a库,其他库正常动态链接,因此ldd能够查看动态库。

想要静态链接得加 -static

[jkl@host roommate]$ gcc main.c -I ./mylib/include/ -L ./mylib/lib -lmyc -static
[jkl@host roommate]$ ls
a.out  log.txt  main.c  mylib
[jkl@host roommate]$ ldd a.outnot a dynamic executable
[jkl@host roommate]$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=b10e09a9d03b05ebc14934c15a9d8b7071c94c29, not stripped

-static的意义是什么?

必须强制添加,因为将我们的程序进行静态链接,这要求我们链接的任何库都必须提供对应的静态库版本。


http://www.ppmy.cn/news/1538496.html

相关文章

Hi3061M——不定长串口接收实现

这里写目录标题 前言串口接收流程串口中断函数ReadITCallBack1中断接收函数 补充结果展示 前言 Hi3061M给了很多相关的串口案例&#xff0c;但大多数是定长的&#xff0c;指定长度进行接收读取&#xff0c;而实际需求往往需要用到不定长的接收。 串口接收流程 首先介绍下Hi3…

rpa批量发送邮件如何通过编辑器编发邮件?

rpa批量发送邮件的技巧&#xff1f;怎么使用rpa邮箱群发助手&#xff1f; 手动发送邮件变得越来越繁琐且效率低下。为了解决这一问题&#xff0c;越来越多的企业开始采用RPA技术来批量发送邮件。AokSend将详细探讨如何通过编辑器来实现rpa批量发送邮件的功能&#xff0c;从而提…

C#WPF自定义表盘实例

本文实现C#WPF自定义美观的表盘。 先看效果 目录 表盘一 表盘二 窗体中使用 表盘一 <UserControlx:Class="MyControl.ucSpeedDialPlate"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsof…

社区团购便利店蔬菜超市分销商城小程序

#分销 裂变分销&#xff0c;智能锁粉快速沉淀客户 #团队分红 拓展销售渠道&#xff0c;增强品牌知名度&#xff0c;提升团队成员宣传积极性&#xff0c;打造人气商城。 #供应商 供应关系搭建&#xff0c;供应周期管理 #积分商城 完成积分兑换&#xff0c;实现积分价…

网络安全之XXE攻击

0x01 什么是 XXE 个人认为&#xff0c;XXE 可以归结为一句话&#xff1a;构造恶意 DTD 介绍 XXE 之前&#xff0c;我先来说一下普通的 XML 注入&#xff0c;这个的利用面比较狭窄&#xff0c;如果有的话应该也是逻辑漏洞。 既然能插入 XML 代码&#xff0c;那我们肯定不能善罢…

金融壹账通亮相2024东亚保险大会 深度参与粤港澳大湾区保险创新探讨

近日&#xff0c;金融壹账通受邀参加在香港举行的2024东亚保险大会 (EAIC)。该会议自1962年创办至今&#xff0c;已成为亚太地区规模最大的保险行业盛会之一&#xff0c;吸引了全球保险和金融领域的众多领导者&#xff0c;旨在共同探讨数字化转型与创新对行业发展的深远影响。金…

跟踪用户状态,http协议无状态 Cookie HttpSession,Session和Cookie的关系

1.概念分析 跟踪用户状态指的是web应用能够分辨请求属于哪个用户&#xff0c;进而记录用户的状态&#xff0c;从而为用户提供连续的针对性的服务。比如有多个客户在同一个购物网站上购物&#xff0c;每一个用户都会有一个虚拟的购物车。当某个客户发送请求将商品添加到购物车时…

华为OD机试真题---单词接龙

问题描述&#xff1a; 单词接龙是一种有趣的文字游戏。在这个问题中&#xff0c;我们需要按照特定规则进行单词接龙&#xff0c;并找出最长的单词串。规则如下&#xff1a; 1、接龙的单词首字母必须与前一个单词的尾字母相同。 2、当有多个首字母相同的单词可选时&#xff0c;…