马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
1.什么是库
库是写好的现有的,成熟的,可以复⽤的代码。实际中每个步伐都要依赖很多根本的底层库,不大概每个⼈的代码都从零开始,因此库的存在意义非同平常。本质上来说库是⼀种可执⾏代码的⼆进制情势,可以被操纵体系载⼊内存执⾏。库有两种:
- 静态库:.a[Linux] .lib[windows]
- 动态库:.so[Linux] .dll[windows]
在Ubuntu中的C语言,C++动静态库如下:- // C
- $ ls -l /lib/x86_64-linux-gnu/libc-2.31.so
- -rwxr-xr-x 1 root root 2029592 May 1 02:20 /lib/x86_64-linux-gnu/libc-2.31.so
- $ ls -l /lib/x86_64-linux-gnu/libc.a
- -rw-r--r-- 1 root root 5747594 May 1 02:20 /lib/x86_64-linux-gnu/libc.a
- //C++
- $ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.so -l
- lrwxrwxrwx 1 root root 40 Oct 24 2022 /usr/lib/gcc/x86_64-linuxgnu/9/libstdc++.so -> ../../../x86_64-linux-gnu/libstdc++.so.6
- $ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a
- /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a
复制代码 我预备了四份文件:my_stdio.c my_stdio.h my_string.c my_string.h为反面的章节做预备,源码如下:- // my_stdio.h
- #pragma once
- #define SIZE 1024
- #define FLUSH_NONE 0
- #define FLUSH_LINE 1
- #define FLUSH_FULL 2
- struct IO_FILE
- {
- int flag; // 刷新⽅式
- int fileno; // ⽂件描述符
- char outbuffer[SIZE];
- int cap;
- int size;
- };
- typedef struct IO_FILE mFILE;
- mFILE *mfopen(const char *filename, const char *mode);
- int mfwrite(const void *ptr, int num, mFILE *stream);
- void mfflush(mFILE *stream);
- void mfclose(mFILE *stream);
- // my_stdio.c
- #include "my_stdio.h"
- #include <string.h>
- #include <stdlib.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <fcntl.h>
- #include <unistd.h>
- mFILE *mfopen(const char *filename, const char *mode)
- {
- int fd = -1;
- if(strcmp(mode, "r") == 0)
- {
- fd = open(filename, O_RDONLY);
- }
- else if(strcmp(mode, "w")== 0)
- {
- fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
- }
- else if(strcmp(mode, "a") == 0)
- {
- fd = open(filename, O_CREAT|O_WRONLY|O_APPEND, 0666);
- }
- if(fd < 0) return NULL;
- mFILE *mf = (mFILE*)malloc(sizeof(mFILE));
- if(!mf)
- {
- close(fd);
- return NULL;
- }
- mf->fileno = fd;
- mf->flag = FLUSH_LINE;
- mf->size = 0;
- mf->cap = SIZE;
- return mf;
- }
- void mfflush(mFILE *stream)
- {
- if(stream->size > 0)
- {
- // 写到内核⽂件的⽂件缓冲区中!
- write(stream->fileno, stream->outbuffer, stream->size);
- // 刷新到外设
- fsync(stream->fileno);
- stream->size = 0;
- }
- }
- int mfwrite(const void *ptr, int num, mFILE *stream)
- {
- // 1. 拷⻉
- memcpy(stream->outbuffer+stream->size, ptr, num);
- stream->size += num;
- // 2. 检测是否要刷新
- if(stream->flag == FLUSH_LINE && stream->size > 0 && stream->outbuffer[stream->size-1]== '\n')
- {
- mfflush(stream);
- }
- return num;
- }
- void mfclose(mFILE *stream)
- {
- if(stream->size > 0)
- {
- mfflush(stream);
- }
- close(stream->fileno);
- }
- // my_string.h
- #pragma once
- int my_strlen(const char *s);
- // my_string.c
- #include "my_string.h"
- int my_strlen(const char *s)
- {
- const char *end = s;
- while(*end != '\0')end++;
- return end - s;
- }
复制代码 2. 静态库
- 静态库(.a):步伐在编译链接的时间把库的代码链接到可执⾏⽂件中,步伐运⾏的时间将不再须要静态库。
- 个可执⾏步伐大概⽤到很多的库,这些库运⾏有的是静态库,有的是动态库,⽽我们的编译默以为动态链接库,只有在该库下找不到动态.so的时间才会采⽤同名静态库。我们也可以使⽤ gcc的 -static 强转设置链接静态库。
2.1. 静态库的天生
- // Makefile
- libmystdio.a:my_stdio.o my_string.o
- @ar -rc $@ $^
- @echo "build $^ to $@ ... done"
- %.o:%.c
- @gcc -c $<
- @echo "compling $< to $@ ... done"
- .PHONY:clean
- clean:
- @rm -rf *.a *.o stdc*
- @echo "clean ... done"
- .PHONY:output
- output:
- @mkdir -p stdc/include
- @mkdir -p stdc/lib
- @cp -f *.h stdc/include
- @cp -f *.a stdc/lib
- @tar -czf stdc.tgz stdc
- @echo "output stdc ... done"
复制代码 步调1:先将.c文件编译成.o文件
步调2:将.o文件举行打包形成.a文件
步调3:将.a文件和.h文件举行打包
步调4:在须要使用时可以将静态库举行压缩解包使用。
2.2. 静态库的使用
- // 任意⽬录下,新建
- // main.c,引⼊库头⽂件
- #include "my_stdio.h"
- #include "my_string.h"
- #include <stdio.h>
- int main()
- {
- const char *s = "abcdefg";
- printf("%s: %d\n", s, my_strlen(s));
- mFILE *fp = mfopen("./log.txt", "a");
- if(fp == NULL) return 1;
- mfwrite(s, my_strlen(s), fp);
- mfwrite(s, my_strlen(s), fp);
- mfwrite(s, my_strlen(s), fp);
- mfclose(fp);
- return 0;
- }
复制代码 使用场景1:头⽂件和库⽂件安装到体系路径下使用场景2:头⽂件和库⽂件和我们⾃⼰的源⽂件在同⼀个路径下- $ gcc main.c -L. -lmymath
复制代码 使用场景3:头⽂件和库⽂件有⾃⼰的独⽴路径- gcc main.c -I头⽂件路径 -L库⽂件路径 -lmymath
复制代码
- -L: 指定库路径。
- -I: 指定头⽂件搜刮路径。
- -l: 指定库名。
- 测试⽬标⽂件⽣成后,静态库删掉,步伐照旧可以运⾏。
- -static选项。
- 库⽂件名称和引⼊库的名称:去掉前缀 lib ,去掉后缀 .so , .a ,如: libc.so -> c
3.动态库
- 动态库(.so):步伐在运⾏的时间才去链接动态库的代码,多个步伐共享使⽤库的代码。
- ⼀个与动态库链接的可执⾏⽂件仅仅包罗它⽤到的函数⼊⼝地点的⼀个表,⽽不是外部函数地点⽬标⽂件的整个呆板码。
-
在可执⾏⽂件开始运⾏从前,外部函数的呆板码由操纵体系从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
- 动态库可以在多个步伐间共享,以是动态链接使得可执⾏⽂件更⼩,节省了磁盘空间。操纵体系采⽤捏造内存机制答应物理内存中的⼀份动态库被要⽤到该库的全部进程共⽤,节省了内存和磁盘空间。
3.1. 动态库的天生
- // Makefile
- libmystdio.so:my_stdio.o my_string.o
- gcc -o $@ $^ -shared
- %.o:%.c
- gcc -fPIC -c $<
- .PHONY:clean
- clean:
- @rm -rf *.so *.o stdc*
- @echo "clean ... done"
- .PHONY:output
- output:
- @mkdir -p stdc/include
- @mkdir -p stdc/lib
- @cp -f *.h stdc/include
- @cp -f *.so stdc/lib
- @tar -czf stdc.tgz stdc
- @echo "output stdc ... done"
复制代码
- shared: 表⽰⽣成共享库格式。
- fPIC:产⽣位置⽆关码(position independent code)
- 库名规则:libxxx.so
- // 场景1:头⽂件和库⽂件安装到体系路径下
- $ gcc main.c -lmystdio// 场景2:头⽂件和库⽂件和我们⾃⼰的源⽂件在同⼀个路径下
- $ gcc main.c -L. -lmymath // 从左到右搜刮-L指定的⽬录// 场景3:头⽂件和库⽂件有⾃⼰的独⽴路径$
- gcc main.c -I头⽂件路径 -L库⽂件路径 -lmymath$ ldd libmystdio.so // 检察库大概可执⾏步伐的依赖linux-vdso.so.1 => (0x00007fffacbbf000)libc.so.6 => /lib64/libc.so.6 (0x00007f8917335000)/lib64/ld-linux-x86-64.so.2 (0x00007f8917905000)// 以场景2为例$ lltotal 24-rwxrwxr-x 1 whb whb 8592 Oct 29 14:50 libmystdio.so-rw-rw-r-- 1 whb whb 359 Oct 19 16:07 main.c-rw-rw-r-- 1 whb whb 447 Oct 29 14:50 my_stdio.h-rw-rw-r-- 1 whb whb 447 Oct 29 14:50 my_string.h$ gcc main.c -L. -lmystdio$ lltotal 36-rwxrwxr-x 1 whb whb 8600 Oct 29 14:51 a.out-rwxrwxr-x 1 whb whb 8592 Oct 29 14:50 libmystdio.so-rw-rw-r-- 1 whb whb 359 Oct 19 16:07 main.c-rw-rw-r-- 1 whb whb 447 Oct 29 14:50 my_stdio.h-rw-rw-r-- 1 whb whb 447 Oct 29 14:50 my_string.hwzh@hcss-ecs-c6ff:$ ./a.out
复制代码 3.3. 库运行搜刮路径
3.3.1. 标题
在Linux体系中,动态链接库的搜刮路径是怎样设置的。通常,当步伐运行时须要加载共享库(.so文件),体系会按照肯定的次序在特定的路径下查找这些库文件。如果找不到对应的库,步伐会报错,比如“error while loading shared libraries”。
3.3.2. 办理方案
- 拷⻉ .so ⽂件到体系共享库路径下, ⼀般指 /usr/lib、/usr/local/lib、/lib64 等路径。
- 向体系共享库路径下建⽴同名软毗连。
- 更改情况变量: LD_LIBRARY_PATH
- ldconfig⽅案:设置/ etc/ld.so.conf.d/ ,ldconfig更新。
4. 目标文件
编译和链接这两个步调,在Windows下被我们的IDE封装的很美满,我们⼀般都是⼀键构建⾮常⽅便,但⼀旦遇到错误的时间呢,尤其是链接相干的错误,很轻易就束⼿⽆策了。在Linux下,我们之前也学习过怎样通过gcc编译器来完成这⼀系列操纵。
可以看到,在编译之后会⽣成两个扩展名为 .o 的⽂件,它们被称作⽬标⽂件。要留意的是如果我们修改了⼀个源⽂件,那么只须要单独编译它这⼀个,⽽不须要浪费时间重新编译整个⼯程。⽬标⽂件是⼀个⼆进制的⽂件,⽂件的格式是 ELF ,是对⼆进制代码的⼀种封装。
|