第一步:虚拟机安装 gcc、ar 工具并验证

0.基本介绍
gcc 是干什么的?
gcc 是 Linux 下的 C 语言编译器,核心作用是把你写的 .c 源代码文件(比如 add.c、main.c),转换成电脑能直接执行的 “机器码” 文件(比如 .o 目标文件、可执行文件)。
ar 是干什么的?
4 个 .o 文件是 “零散的零件”,ar 把它们 “组装成一个完整的工具箱”(静态库)。这样后续编译主程序时,不用逐个链接 4 个 .o 文件,直接链接这个 “工具箱” 就行,更方便管理。

1.先更新软件源
sudo apt update

2.安装 gcc 和 ar 工具
sudo apt install gcc binutils -y

3.验证工具是否安装成功
gcc -v
ar -V
PS:
gcc 的 -v(小写)是 --version 的缩写,作用是 “显示版本信息”。
ar 的 -V(大写)也是 “显示版本信息”; ar 工具的小写 -v 是另一个功能 ——“verbose(详细模式)”,比如打包静态库时用 ar rcsv libcalc.a *.o,会显示打包的详细过程(哪个 .o 文件被加入了);

第二步:验证 adb 连通性 + 确认开发板运行环境

1. 先确认虚拟机已安装 adb

#Ubuntu/Debian 系统安装 adb
sudo apt install android-tools-adb -y

#验证 adb 安装成功
adb –version

  1. 连接开发板,验证 adb 连通性
    在这里插入图片描述

  2. 确认开发板运行环境
    在这里插入图片描述
    -armv7l 就是开发板的 “硬件身份证”,告诉我们 “这个开发板只能运行 ARM32 位 架构的程序”。 因为虚拟机的 CPU 大多是 x86 架构(和开发板的 ARM 架构不兼容)—— 就像 “安卓 APP 不能直接在 Windows 电脑上运行” 一样,虚拟机编译的 x86 架构程序,开发板根本跑不了。

-/lib/ld-2.30.so:动态链接器的 “真身”(实际执行的文件),2.30 是它的版本号;
/lib/ld-linux-armhf.so.3:动态链接器的 “软链接”(相当于 Windows 的快捷方式),作用是让程序能快速找到 “真身”(程序默认会找 ld-linux-armhf.so.3 这个名字,不用记复杂的版本号)。是开发板的动态链接器,动态库程序(main_dynamic)运行的必备工具

第三步:文件创建任务

1.创建专属工作目录
mkdir -p ~/lib_test
cd ~/lib_test
pwd

2.创建头文件 calc.h
语法
在这里插入图片描述

cat > calc.h << EOF
#ifndef CALC_H
#define CALC_H

// 声明4个运算函数(告诉编译器函数的格式)
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);

#endif
EOF

3. 创建功能文件
add.c
cat > add.c << EOF
#include “calc.h” // 包含头文件,匹配函数声明

// 实现加法函数:返回a+b的结果
int add(int a, int b) {
return a + b;
}
EOF

sub.c
cat > sub.c << EOF
#include “calc.h”

// 实现减法函数:返回 a - b 的结果
int sub(int a, int b) {
return a - b;
}
EOF

mul.c
cat > mul.c << EOF
#include “calc.h”

// 实现乘法函数:返回 a * b 的结果
int mul(int a, int b) {
return a * b;
}
EOF

div.c
cat > div.c << EOF
#include “calc.h”

// 实现除法函数:返回 a / b 的结果,加除0保护
int div(int a, int b) {
if (b == 0) { // 如果除数b是0,返回-1标识错误
return -1;
}
return a / b; // 正常除法运算
}
EOF

4. 创建主程序 main.c
cat > main.c << EOF
#include <stdio.h> // 包含标准输入输出头文件(printf函数需要)
#include “calc.h”

int main() {
// 测试用例1:正常运算(a=10, b=5)
int a = 10, b = 5;
printf(“===== 正常运算测试 =====\n”);
printf(“a=%d, b=%d\n”, a, b);
printf(“加法:%d + %d = %d\n”, a, b, add(a, b));
printf(“减法:%d - %d = %d\n”, a, b, sub(a, b));
printf(“乘法:%d * %d = %d\n”, a, b, mul(a, b));
printf(“除法:%d / %d = %d\n”, a, b, div(a, b));

// 测试用例2:除0场景(验证div函数的保护逻辑)
b = 0;
printf("\n===== 除0测试 =====\n");
printf("a=%d, b=%d\n", a, b);
printf("除法:%d / %d = %d (返回-1表示除0错误)\n", a, b, div(a, b));

return 0;  

}
EOF

第四步:编译静态库并测试 (虚拟机)

1.将 4 个功能.c 文件编译为.o 目标文件
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o

2. 打包.o 文件为静态库 libcalc.a
ar rcsv libcalc.a add.o sub.o mul.o div.o
在这里插入图片描述
4. 编译主程序并链接静态库,生成可执行文件
gcc main.c -o main_static -lcalc -L.
在这里插入图片描述

PS:当你用 -lxxx(小写 L + 库名)时,编译器会自动在前面补 lib,后面补 .a(静态库)或 .so(动态库),然后去指定路径找这个文件。

5. 运行可执行程序 main_static,查看结果.在这里插入图片描述

第五步:传输到开发板测试静态库

1. 用 adb 把文件传到开发板
#1. 先在开发板的 /tmp 目录创建临时文件夹(避免权限问题)
adb shell mkdir -p /tmp/lib_test

#2. 传输静态库文件 libcalc.a 到开发板 /tmp/lib_test
adb push libcalc.a /tmp/lib_test/

#3. 传输头文件 calc.h 到开发板 /tmp/lib_test
adb push calc.h /tmp/lib_test/

#4. 传输可执行程序 main_static 到开发板 /tmp/lib_test
adb push main_static /tmp/lib_test/

2. 开发板端运行 main_static
cd /tmp/lib_test
chmod +x main_static
./main_static
在这里插入图片描述
报错,要交叉编译

3. 安装 ARM32 位交叉编译器
回到虚拟机端
sudo apt update
sudo apt install gcc-arm-linux-gnueabihf

4. 用交叉编译器重新编译
在这里插入图片描述

5. 传输 ARM 版程序到开发板并运行
adb push main_static_arm /tmp/lib_test/

adb shell
cd /tmp/lib_test

chmod +x main_static_arm

./main_static_arm
在这里插入图片描述

第六步:动态库编译与测试任务

#1.虚拟机端:编译 ARM 版动态库 + 可执行程序
#1. 进入工作目录(~/lib_test)
cd ~/lib_test

#2. 编译ARM架构+位置无关的.o文件(动态库必需-fPIC)
arm-linux-gnueabihf-gcc -c -fPIC add.c -o add_arm.o
arm-linux-gnueabihf-gcc -c -fPIC sub.c -o sub_arm.o
arm-linux-gnueabihf-gcc -c -fPIC mul.c -o mul_arm.o
arm-linux-gnueabihf-gcc -c -fPIC div.c -o div_arm.o

PS:
-fPIC 是 Position Independent Code 的缩写,翻译为位置无关代码,核心作用是:
让编译出的代码「不绑定固定内存地址」,无论动态库(.so)被系统加载到内存的哪个位置,代码都能正常运行。

#3. 生成ARM版动态库(命名:libcalc_arm.so)
arm-linux-gnueabihf-gcc -shared -o libcalc_arm.so add_arm.o sub_arm.o mul_arm.o div_arm.o

PS:对比静态库
#静态库核心打包命令(替代动态库的 -shared 步骤)
ar rcsv libcalc_arm.a add_arm.o sub_arm.o mul_arm.o div_arm.o
在这里插入图片描述

#4. 编译ARM版可执行程序
(命名:main_dynamic_arm,链接libcalc_arm.so,加-rpath避免开发板配路径)
arm-linux-gnueabihf-gcc main.c -o main_dynamic_arm -lcalc_arm -L. -Wl,-rpath=./
PS:
如果编译时不加 -Wl,-rpath=./,运行 main_dynamic_arm 前,必须在同一个终端里执行这句:export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
静态库编译时已把代码打包进程序,运行时完全不依赖外部文件,所以既不用 -rpath,也不用 export LD_LIBRARY_PATH,动态库在程序运行时才被加载,系统必须能找到它的物理文件才能执行程序

2.开发板端,验证结果
跟前面一样用adb传输,然后验证,这里因为前面加了-Wl,-rpath=./,会在当前目录先查找
在这里插入图片描述
动态库程序离开对应的.so 文件就无法执行,,静态库 “删了.a 也能跑” 形成鲜明对比。
在这里插入图片描述

第七步:静态库和动态库对比

1. 对比静态库与动态库的编译流程差异

在这里插入图片描述

  • 动态库必须加-fPIC(生成位置无关代码),静态库无需此参数;原因:动态库运行时加载地址不固定,需适配任意内存地址,静态库地址固定。
  • 静态库用ar归档工具(仅合并.o 文件,无编译),动态库用编译器(-shared 参数编译生成可动态加载的.so);输出文件:静态库是.a(归档文件),动态库是.so(动态链接库)。
  • 静态库:编译器将.a 中的代码完整复制到程序中,生成的程序无外部依赖;动态库:编译器仅在程序中记录 “依赖 libcalc_arm.so”,不复制库代码;动态库可加-Wl,-rpath=./(内置库路径),静态库无需。
    2. 对比 main_static 与 main_dynamic 的文件体积
    在这里插入图片描述
    测试程序功能太简单(只有加减乘除几个基础函数),库代码的体积占比极低 —— 静态库打包进去的代码量太少,所以和只记录依赖的动态库版体积差距不明显。一般动态库版体积小。
    在这里插入图片描述
    3. 记录测试中遇到的问题及解决方法
    问题 1:开发板运行main_dynamic_arm时,报 “libcalc_arm.so: cannot open shared object file”
    原因:动态库程序运行时需系统找到.so文件,开发板默认只搜系统目录,不包含当前工作目录/tmp/lib_test;
    问题 2:查看main_static_arm和main_dynamic_arm体积时,差异远小于预期
    原因:测试程序功能极简(仅加减乘除),库代码体积占比极低,程序 “基础骨架”(8K 左右)掩盖了体积差异;且编译器自动做了 “死代码消除”,静态库仅打包实际用到的少量代码;
    问题 3:使用ar命令时误加-o参数,报 “invalid option – ‘o’”
    原因:ar是归档工具,非编译器,不识别-o(编译器用-o指定输出,ar直接将输出名放在参数第二位);
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐