目录
前言
C语言程序编译的过程
- 预处理:解释并展开源程序当中的所有的预处理指令,此时生成 *.i 文件。
- 编译:词法和语法的分析,生成对应硬件平台的汇编语言文件,此时生成 *.s 文件。
- 汇编:将汇编语言文件翻译为对应处理器的二进制机器码,此时生成 *.o 文件。
- 链接:将多个 *.o 文件合并成一个不带后缀的可执行文件。
gcc hello.c -o hello.i -E 👉预处理
gcc hello.i -o hello.s -S 👉编译
gcc hello.s -o hello.o -c 👉汇编
gcc hello.o -o hello -lc 👉链接
ELF格式
对于上述编译过程,重点关注最后一步库文件的链接(gcc hello.o -o hello -lc):链接实际上是将多个.o文件合并在一起的过程。这些 *.o 文件合并前是 ELF 格式,合并后也是 ELF 格式。
ELF全称是 Executable and Linkable Format,即可执行可链接格式。ELF文件由多个不同的段(section)组成,如下图所示:
ELF格式的合并,实际上就是将多个文件中各自对应的段合并在一起,形成一个统一的ELF文件。在此过程中,必然需要对各个 *.o 文件中的静态数据(包括常量)、函数入口的地址做统一分配和管理,这个过程就叫做 重定位,因此未经链接的单独的 *.o 文件又被称为可重定位文件,经过链接处理合并了相同的段的文件称为可执行文件。
库的本意是library图书馆,库文件就是一个由很多 *.o 文件堆积起来的集合。
# 查看文件格式头部信息
readelf -h a.out
# 查看各个section信息
readelf -S a.out
# 查看符号表
readelf -s a.out
静态库与动态库
静态库:libx.a
动态库:liby.so
库文件的名称遵循这样的规范:lib库名.后缀
代码复用:开发者可以将常用的代码段编译成库,供多个程序使用,避免重复编写相同的代码。
模块化:通过将程序划分为多个库,可以更好地管理和维护代码。每个库可以独立更新和优化,而不影响其他部分。
节省资源:动态库可以在多个程序之间共享,不需要每个程序都包含库的副本,这样可以节省磁盘空间和内存。
易于维护和更新:当库需要更新或修复bug时,只需要更新库文件本身,所有使用该库的程序都可以在不重新编译的情况下获得更新。
提高程序性能:库中的代码通常由专家优化,可以提供高效的实现,从而提高整个程序的性能。
保护代码的知识产权: 封装到库中的代码都是已经编译好的二进制代码,用户无法查看其源码。
其中,lib是任何库文件都必须有的前缀,库名就是库文件真正的名称,比如上述例子中两个库文件分别叫x和y,在链接它们的时候写成 -lx 和 -ly ,后缀根据静态库和动态库,可以是 .a 或者 .so:
- 静态库的后缀:.a (archive,意即档案)
- 动态库的后缀:.so (share object,意即共享对象)
注意:不管是静态库,还是动态库,都是可重定位文件 *.o 的集合。
不管是动态库还是静态库,它们都是 *.o 文件的集合。如果把一个 *.o 文件比作一本图书,那么库文件就是书店或图书馆,静态库和动态库的关系和区别是:
- 静态库(相当于书店,只卖不借)
- 原理:编译时,库中的代码将会被复制到每一个程序中
- 优点:程序不依赖于库、执行效率稍高
- 缺点:消耗存储空间、无法对用户升级迭代
- 动态库(相当于图书馆,只借不卖)
- 原理:编译时,程序仅确认库中功能模块的匹配关系,并未复制
- 缺点:程序依赖于库、执行效率稍低
- 优点:节省存储空间、方便对用户升级迭代
表面上看,静态库和动态库各有千秋,彼此的优缺点是相对的,但在实际应用中,动态库应用场合要远多于静态库,因为虽然动态库的运行时装载特性会使得程序性能有略微的下降,但换来的是不仅仅节省了大量的存储空间,更重要的是使得主程序和库松耦合,不互相捆绑,当库升级的时候,应用程序无需任何改动即可获得新版库文件的功能,这极大地提高了程序的灵活性。
1.动态库以 xxx.so 结尾 , 静态库以 xxxx.a 结尾。
2.动态库在编译阶段不会把代码编译进源程序中。
3.静态库在编译阶段会把代码编译进源程序中。
4.静态库的编译文件比较大,动态库编译的文件比较小。
5.动态库效率比静态库高,但是动态库的环境配置比较麻烦。
静态库
静态库制作
第一步,操作并编译
1.编写源码的.c 与 .h 头文件
xxxx.c
xxxx.h
2.把.c 文件编译成 .o 文件
gcc xxxx.c -o xxxx.o -c
3.把所有的xxx.o 文件封装到一个静态库 xxxx.a 中
ar crs lib??????.a xxx.o ......
例子:ar crs libstring.a string.o #把string.o 文件封装到静态库中
提示: ar -t 静态库名称 #查看当前静态库包含的 .o 文件
静态库的操作命令
ar t libx.a #查看静态库中的文件
ar d libx.a b.o #删除静态库中的文件
ar r libx.a b.o #增加文件到静态库中
ar x libx.a #(x意即extract,将库中所有的*.o文件释放出来)
ar x libx.a a.o #(指定释放库中的a.o文件)
第二步,使用库
1.把库文件 和 所有头文件放入一个文件夹中
harold@PC:lib$ ls
a.out file.h lcd.h libPicture.a list.h main.c touch.h
2.链接静态库
harold@PC:lib$ gcc main.c -L./ -lPicture
-L : 说明当前静态库所在的目录
-l : 说明当前静态库的名称 libPicture.a 原名 -lPicture 链接名
静态库使用
harold@PC:lib$ gcc main.c -L./ -lPicture
-L : 说明当前静态库所在的目录
-l : 说明当前静态库的名称 libPicture.a 原名 -lPicture 链接名
-L 用于告诉编译器库文件在哪个路径下寻找
-l 用于告诉编译器你需要使用到哪个库文件
动态库
//生成*.o文件
gcc sum.c -o sum.o -c -fpic
gcc sub.c -o sub.o -c -fpic
//归档成为动态库
gcc -shared -fpic -o libmath_share.so sub.o sum.o
//复制动态库到/usr/lib或者/lib
sudo cp libmath_share.so /usr/lib
//复制动态库到/usr/lib或者/lib的编译main的做法
gcc main.c -o main -lmath_share
//指定动态库文件路径的方法
arm-gcc main.c -o main -lmath_share -Wl,-rpath=/home/harold/C_Linux/009day/lib_file
动态库制作编译
1.编写源码的.c 与 .h 头文件
xxxx.c
xxxx.h
2.把.c 文件编译成 .o 文件
gcc xxxx.c -o xxxx.o -c -fPIC
3.把 所有的.o 封装成 xxx.so 动态库 , -fPIC与内存地址无关,方便跨平台使用
gcc -shared -fPIC -o libxxxx.so xxxx.o .......
-fPIC与内存地址无关
动态库使用
1.把库文件 和 所有头文件放入一个文件夹中
harold@PC:lib$ ls
a.out file.h lcd.h libPicture.a list.h main.c touch.h
2.链接静态库
harold@PC:lib$ gcc main.c -L./ -lPicture
-L : 说明当前动态库所在的目录
-l : 说明当前动态库的名称 libPicture.a 原名 -lPicture 链接名
注意:动态库链接完毕后,必须把库文件放入系统的 /lib 目录下程序才能正常运行!
jpeg库
提供JPEG图像编码/解码,支持压缩参数调整(如质量、色彩空间)。libjpeg
解压库源码&配置源码
1.解压源码
mkdir ~/jpegsrc
tar -xvf jpegsrc.v9d.tar.gz -C ~/jpegsrc/
2.配置源码
cd ~/jpegsrc/jpeg-9d/ #1.进入源码目录
./configure --help #2.查看配置帮助文档
配置参数说明👇
........
--prefix=PREFIX install architecture-independent files in PREFIX
[/usr/local] 安装库的路径
--host=HOST cross-compile to build programs to run on HOST [BUILD]
............
mkdir /home/harold/jpeglib #3.0创建一个安装目录
echo 'export PATH=$PATH:/usr/local/arm/5.4.0/usr/bin' >> ~/.bashrc #3.1配置PATH环境变量
source ~/.bashrc #加载配置文件
./configure --prefix=/home/gec/jpeglib --host=arm-linux #3.2⭐重点:开始配置
make #4.编译 👉 调用makefile 文件生成 .o 和动态库与静态库
make install #5.安装
jpeg库的使用
1.解压配置好的库文件到家目录
tar -xvf jpeglib.tar.gz -C /home/gec
2.把调用官方的示例代码,进行解码
harold@PC:官方解码例子$ ls
example.c (官方解码例子) include (头文件) main.c (主函数)
3.编译官方示例代码 👉编译过程中出现的问题自己解决一下
harold@PC:官方解码例子$ arm-linux-gcc main.c example.c -I./include -L/home/harold/jpeglib/lib -ljpeg
4.下载可执行文件到开发板中运行
[root@harold /]#./a.out
./a.out: error while loading shared libraries: libjpeg.so.9 ❌出现错误, 解决方法:把jpeg库下载到开发板的 /lib 目录
jpeg 解码源码分析
//解码结构体
struct jpeg_decompress_struct {
JDIMENSION image_width; /* 图片宽度*/
JDIMENSION image_height; /* 图片高度*/
int num_components; /* # of color components in JPEG image 色深*/
}
//1.读取jpeg头数据
(void)jpeg_read_header(&cinfo, TRUE); // 读取jpeg文件头
// 打印图片的信息
printf("%d %d %d\n", cinfo.image_width, cinfo.image_height, cinfo.num_components);
jpeg 转 rgb 算法设计
char rgb[480][800 * 3] = {0};
int y = 0; // 当前行
// 一行一行的解码
while (cinfo.output_scanline < cinfo.output_height)
{
printf("当前解码行 %d : 总行数 %d\n", cinfo.output_scanline, cinfo.output_height);
(void)jpeg_read_scanlines(&cinfo, buffer, 1); // 解码一行jpeg数据
for (int x = 0; x < 800 * 3; x++)
{
rgb[y][x] = buffer[0][x];
}
y++; // 行数增加
}
// 把RGB转换为ARGB
char argb[480][800 * 4];
for (int y = 0; y < 480; y++)
{
for (int x = 0; x < 800; x++)
{
argb[y][2 + x * 4] = rgb[y][0 + x * 3]; // R
argb[y][1 + x * 4] = rgb[y][1 + x * 3]; // G
argb[y][0 + x * 4] = rgb[y][2 + x * 3]; // B
argb[y][3 + x * 4] = 0; // A
}
}