【Linux系统编程】第三十一弹---深入理解静态库:从零开始制作与高效使用的完全指南
怎么做静态库,怎么使用静态库~~~
✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】
目录
1、静态库
1.1、怎么做静态库
在Linux环境下,通常使用GCC(GNU Compiler Collection)编译器来编译源代码,并使用
ar
(archiver)工具来创建静态库。
-
编写源代码:首先,你需要有一些源代码文件,比如 x.c ,y.c ,z.c
-
编译源代码为对象文件:使用GCC编译器将源代码编译为目标文件(.o文件)。
-
创建静态库:使用
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的意义是什么?
必须强制添加,因为将我们的程序进行静态链接,这要求我们链接的任何库都必须提供对应的静态库版本。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)