【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 4

typedef 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.a

3 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.out
	linux-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.out
	not 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的意义是什么?

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

相关推荐
To_OC3 小时前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
2401_841495644 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
Yana.nice4 小时前
Linux 只保留 30 天内日志(find命令删除日志文件)
linux·运维·chrome
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
2601_960567966 小时前
电商套图自动化效率的工程量化分析——从逐张生成到批量套图的架构演进
运维·架构·自动化