当前位置: 首页 > news >正文

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

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

💗系列专栏: 【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的意义是什么?

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

相关文章:

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

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、静态库 1.1、怎么做静态库 1.2、怎么使用静态库 1、静态库 1.1、怎么做静态库 在Linux环境下&#xff0c;通常使用GCC&am…...

FFmpeg 简介及其下载安装步骤

目录 一、FFmpeg 简介 二、FFmpeg 安装步骤 2.1 打开官网 2.2 选择FFmpeg系统版本 2.3 下载FFmpeg压缩包 2.4 将下载好的压缩包进行解压 2.5 设置环境变量 2.5.1 在搜索栏中搜索【环境变量】&#xff0c;然后单击将其打开 2.5.2 找到系统变量中的【Path】&#xff0c;点…...

使用CSS+SVG实现加载动画

使用CSSSVG实现加载动画 效果展示 CSS知识点 SVG元素使用SVG相关CSS属性运用 整体页面布局 <section><div class"box"><div class"loader"><svg><circle cx"40" cy"40" r"40"></circl…...

物联网(IoT)的未来发展:智能互联时代的到来

物联网&#xff08;IoT&#xff09;的未来发展&#xff1a;智能互联时代的到来 物联网&#xff08;IoT&#xff09;正在迅速改变我们与世界互动的方式。无论是智能家居、智慧城市&#xff0c;还是工业自动化&#xff0c;物联网技术通过设备互联、数据采集和智能控制&#xff0…...

斯坦福 CS229 I 机器学习 I 构建大型语言模型 (LLMs)

1. Pretraining -> GPT3 1.1. Task & loss 1.1.1. 训练 LLMs 时的关键点 对于 LLMs 的训练来说&#xff0c;Architecture&#xff08;架构&#xff09;、Training algorithm/loss&#xff08;训练算法/损失函数&#xff09;、Data&#xff08;数据&#xff09;、Evalu…...

Java->排序

目录 一、排序 1.概念 2.常见的排序算法 二、常见排序算法的实现 1.插入排序 1.1直接插入排序 1.2希尔排序(缩小增量法) 1.3直接插入排序和希尔排序的耗时比较 2.选择排序 2.1直接选择排序 2.2堆排序 2.3直接选择排序与堆排序的耗时比较 3.交换排序 3.1冒泡排序…...

linux 大小写转换

var"TM_card_INFo" # 把变量中的第一个字符换成大写 echo ${var^} # 把变量中的所有小写字母&#xff0c;全部替换为大写 echo ${var^^} # 把变量中的第一个字符换成小写 echo ${var,} # 把变量中的所有大写字母&#xff0c;全部替换为小写 echo ${var,,} 参考…...

Linux——传输层协议

目录 一再谈端口号 1端口号范围划分 2两个问题 3理解进程与端口号的关系 二UDP协议 1格式 2特点 3进一步理解 3.1关于UDP报头 3.2关于报文 4基于UDP的应用层协议 三TCP协议 1格式 2TCP基本通信 2.1关于可靠性 2.2TCP通信模式 3超时重传 4连接管理 4.1建立…...

centos系列,yum部署jenkins2.479.1,2024年长期支持版本

centos系列&#xff0c;yum部署jenkins2.479.1&#xff0c;2024年长期支持版本 0、介绍 注意&#xff1a;jenkins建议安装LTS长期支持版本&#xff0c;而不是安装每周更新版本&#xff0c;jenkins安装指定版本 openjdk官网下载 Index of /jenkins/redhat-stable/ | 清华大学开…...

正则表达式-“三剑客”(grep、sed、awk)

1.3正则表达式 正则表达式描述了一种字符串匹配的模式&#xff0c;可以用来检查一个串是否含有某种子串&#xff0c;将匹配的子串替换或者从某个串中取出符号某个条件的子串等&#xff0c;在linux中代表自定义的模式模版&#xff0c;linux工具可以用正则表达式过滤文本。Linux…...

数智时代的新航向:The Open Group 2024生态系统架构·可持续发展年度大会邀您共筑AI数字新时代

在全球可持续发展和数字化转型双重驱动下&#xff0c;企业正面临着前所未有的挑战与机遇。如何在激烈的市场竞争中&#xff0c;实现业务增长的同时&#xff0c;履行社会责任&#xff0c;达成可持续发展的目标&#xff1f;The Open Group 2024生态系统架构可持续发展年度大会将于…...

TensorFlow 的核心概念

TensorFlow 是一个开源的机器学习框架&#xff0c;由 Google 开发和维护。它提供了一个强大的工具集&#xff0c;用于构建和训练各种机器学习模型。 TensorFlow 的核心概念是计算图&#xff08;Computational Graph&#xff09;。计算图由节点&#xff08;Nodes&#xff09;和…...

SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(动态新增、修改等操作)

SpringBoot教程&#xff08;二十四&#xff09; | SpringBoot实现分布式定时任务之Quartz&#xff08;动态新增、修改等操作&#xff09; 前言数据库脚本创建需要被调度的方法创建相关实体类创建业务层接口创建业务层实现类控制层类测试结果 前言 我这边的SpringBoot的版本为2…...

Matlab详细学习教程 MATLAB使用教程与知识点总结

Matlab语言教程 章节目录 一、Matlab简介与基础操作 二、变量与数据类型 三、矩阵与数组操作 四、基本数学运算与函数 五、图形绘制与数据可视化 六、控制流与逻辑运算 七、脚本与函数编写 八、数据导入与导出 九、Matlab应用实例分析 一、Matlab简介与基础操作 重点内容知识…...

【ELKB】Kibana使用

搭建好ELKB后访问地址&#xff1a;http://localhost:5601 输入账号密码登录以后 左侧导航有home、Analysis、Enterprise search 、Observability、Security、Management home&#xff1a;首页Analysis&#xff1a;工具来分析及可视化数据Enterprise search&#xff1a;企业级搜…...

ChatGPT免费使用:人工智能在现代社会中的作用

随着人工智能技术的不断发展&#xff0c;越来越多的应用程序和工具开始使用GPT作为其语言模型。但是&#xff0c;这些应用程序和工具是否收费&#xff1f;如果是免费的&#xff0c;那么他们是如何盈利的&#xff1f;在本文中&#xff0c;我们将探讨ChatGPT免费使用的背后原理&a…...

腾讯音乐:从 Elasticsearch 到 Apache Doris 内容库升级,统一搜索分析引擎,成本直降 80%

导读&#xff1a; 为满足更严苛数据分析的需求&#xff0c;腾讯音乐借助 Apache Doris 替代了 Elasticsearch 集群&#xff0c;统一了内容库数据平台的内容搜索和分析引擎。并基于 Doris 倒排索引和全文检索的能力&#xff0c;支持了复杂的自定义标签计算&#xff0c;实现秒级查…...

CubeMX的FreeRTOS学习

一、FreeRTOS的介绍 什么是FreeRTOS&#xff1f; Free即免费的&#xff0c;RTOS的全称是Real Time Operating system,中文就是实时操作系统。 注意&#xff1a;RTOS不是指某一个确定的系统&#xff0c;而是指一类的操作系统。比如&#xff1a;us/OS&#xff0c;FreeRTOS&…...

C语言初始:数据类型和变量

、 一.数据类型介绍 人有黄人白人黑人&#xff0c;那么数据呢&#xff1f; 我们大家可以看出谁是黄种人&#xff0c;谁是白种人&#xff0c;谁是黑种人&#xff0c;这是因为他们是类似的。 数据也是有类型的&#xff0c;就譬如整数类型&#xff0c;字符类型&#xff0c;浮点…...

Linux shellcheck工具

安装工具 通过linux yum源下载&#xff0c;可能因为yum源的问题找不到软件包&#xff0c;或者下载的软件包版本太旧。 ShellCheck的源代码托管在GitHub上(推荐下载方式)&#xff1a; GitHub - koalaman/shellcheck: ShellCheck, a static analysis tool for shell scripts 对下…...

FLINK SQL时间属性

Flink三种时间属性简介 在Flink SQL中&#xff0c;时间属性是一个核心概念&#xff0c;它主要用于处理与时间相关的数据流。Flink支持三种时间属性&#xff1a;事件时间&#xff08;event time&#xff09;、处理时间&#xff08;processing time&#xff09;和摄入时间&#…...

android——Groovy gralde 脚本迁移到DSL

1、implementation的转换 implementation com.github.CymChad:BaseRecyclerViewAdapterHelper:*** 转换为 implementation ("com.github.CymChad:BaseRecyclerViewAdapterHelper:***") 2、plugin的转换 apply plugin: kotlin-android-extensions 转换为&#x…...

工程项目管理中的最常见概念!蓝燕云总结!

01 怎么理解工程项目管理&#xff1f; 建设工程项目管理指的是专业性的管理&#xff0c;并非行政事务管理。建设工程项目管理是对工程项目全生命周期的管理&#xff0c;确保项目能够按计划的进度、成本和质量完成。 建设工程项目不同阶段管理的主要内容不同&#xff0c;通常…...

PostgreSQL AUTO INCREMENT

PostgreSQL AUTO INCREMENT 在数据库管理系统中&#xff0c;自动递增&#xff08;AUTO INCREMENT&#xff09;是一种常见特性&#xff0c;用于在插入新记录时自动生成唯一的标识符。PostgreSQL&#xff0c;作为一个功能强大的开源关系数据库管理系统&#xff0c;也提供了类似的…...

24-10-13-读书笔记(二十五)-《一只特立独行的猪》([中] 王小波)用一生来学习艺术

文章目录 《一只特立独行的猪》&#xff08;[中] 王小波&#xff09;目录阅读笔记记录总结 《一只特立独行的猪》&#xff08;[中] 王小波&#xff09; 十月第五篇&#xff0c;放慢脚步&#xff0c;秋季快要过去了&#xff0c;要步入冬季了&#xff0c;心中也是有些跌宕起伏&am…...

Java—继承性与多态性

目录 一、this关键字 1. 理解this 2. this练习 二、继承性 2.1 继承性的理解 2.1.1 多层继承 2.2 继承性的使用练习 2.2.1 练习1 2.2.2 练习2 2.3 方法的重写 2.4 super关键字 2.4.1 子类对象实例化 三、多态性 3.1 多态性的理解 3.2 向下转型与多态练习 四、Ob…...

打通华为认证实验考试“痛点”:备考指南全解析

华为认证体系中的实验考试环节&#xff0c;尤其是针对高端的HCIE认证&#xff0c;是评估考生实践技能的关键部分。这一环节的核心目标是检验考生对华为设备和解决方案的操作熟练度、技术实施技能以及面对现实工作挑战时的问题解决能力。通过在真实环境中进行的实践操作&#xf…...

【软考】子系统划分

目录 1. 子系统划分的原则1.1 子系统要具有相对独立性1.2 子系统之间数据的依赖性尽量小1.3 子系统划分的结果应使数据几余较小1.4 子系统的设置应考虑今后管理发展的需要1.5 子系统的划分应便于系统分阶段实现1.6 子系统的划分应考虑到各类资源的充分利用 2. 子系统结构设计3.…...

【Python】selenium获取鼠标在网页上的位置,并定位到网页位置模拟点击的方法

在使用Selenium写自动化爬虫时&#xff0c;遇到验证码是常事了。我在写爬取测试的时候&#xff0c;遇到了点击型的验证码&#xff0c;例如下图这种&#xff1a; 这种看似很简单&#xff0c;但是它居然卡爬虫&#xff1f;用简单的点触验证码的方法来做也没法实现 平常的点触的方…...

【C++ 真题】B2078 含 k 个 3 的数

含 k 个 3 的数 题目描述 输入两个正整数 m m m 和 k k k&#xff0c;其中 1 < m ≤ 1 0 15 1 \lt m \leq 10^{15} 1<m≤1015&#xff0c; 1 < k ≤ 15 1 \lt k \leq 15 1<k≤15 &#xff0c;判断 m m m 是否恰好含有 k k k 个 3 3 3&#xff0c;如果满足条…...