5、第一个交叉编译项目:使用CMake命令行指定编译器、简单交叉编译Hello World
好,咱们终于到了动手的环节了。
前面几章讲了那么多理论,什么工具链、sysroot、目标三元组……说实话,光看概念确实容易犯困。我自己当年学交叉编译的时候,也是看了半天文档,最后发现——不如直接跑一个Hello World来得实在。
这一章,我们就来干这件事。
目标很简单:在x86的电脑上,用CMake编译出一个能在ARM开发板上运行的Hello World程序。而且,我们暂时不用工具链文件,直接用CMake命令行指定编译器。这样你能更清楚地看到,CMake到底是怎么知道“我要交叉编译”的。
5.1 准备工作:你需要什么?
在开始之前,先确认一下你手头有没有这些东西:
- 一台Linux主机(Ubuntu 20.04或22.04最好,我用的就是这个)
- 安装了CMake(版本3.10以上,建议3.20+)
- 安装了交叉编译器(比如
gcc-arm-linux-gnueabihf) - 一个文本编辑器(vim、vscode都行,你习惯就好)
安装交叉编译器很简单,Ubuntu上一条命令搞定:
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
装完之后,你可以验证一下:
arm-linux-gnueabihf-gcc --version
能看到版本信息,说明工具链已经就绪。
gcc-aarch64-linux-gnu。原理完全一样,只是名字不同。
5.2 写一个Hello World,这你总会吧
先创建一个项目目录,比如叫 hello_cross:
mkdir hello_cross
cd hello_cross
然后写一个最简单的C文件 main.c:
#include <stdio.h>
int main() {
printf("Hello from ARM!\n");
return 0;
}
嗯,就这几行。没什么特别的,对吧?
但有意思的地方在后面——我们怎么让CMake知道,这个程序不是给x86编译的,而是给ARM编译的。
5.3 写CMakeLists.txt,但先不写死编译器
在同一个目录下,创建 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(HelloCross C)
add_executable(hello_cross main.c)
你看,这个文件里完全没有提到ARM,也没有指定任何编译器。它就是一个普普通通的CMake项目。
那怎么让它交叉编译呢?答案就在CMake的命令行参数里。
5.4 关键一步:用命令行指定编译器
我们不在CMakeLists.txt里写死编译器,而是通过 -D 参数在命令行传入。这样做的好处是:同一个CMakeLists.txt,既可以本地编译,也可以交叉编译,完全由你控制。
执行以下命令:
mkdir build_arm
cd build_arm
cmake .. \
-DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc \
-DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++
make
解释一下这几步:
mkdir build_arm:我习惯单独建一个构建目录,保持源码干净cmake ..:指向源码目录-DCMAKE_C_COMPILER=...:告诉CMake,C编译器用ARM版的-DCMAKE_CXX_COMPILER=...:C++编译器同理make:编译
如果一切顺利,你会看到类似这样的输出:
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/hello_cross/build_arm
Scanning dependencies of target hello_cross
[ 50%] Building C object CMakeFiles/hello_cross.dir/main.c.o
[100%] Linking C executable hello_cross
然后你会在 build_arm 目录下得到一个 hello_cross 可执行文件。
5.5 怎么确认它是ARM的?
你可以在主机上直接运行它吗?不行。因为它是ARM指令集的,你的x86电脑跑不了。
那怎么验证?用 file 命令:
file hello_cross
输出应该是这样的:
hello_cross: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped
看到 ARM 了吧?说明它确实是ARM架构的可执行文件。
5.6 我踩过的一个坑
讲个真事。我刚开始用CMake做交叉编译时,犯过一个低级错误——忘了指定C++编译器。
当时项目里既有C文件又有C++文件,我只传了 -DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc,没传 CMAKE_CXX_COMPILER。结果CMake自动检测到了主机上的g++,用它来编译C++文件。链接的时候,ARM的C对象和x86的C++对象混在一起……那错误信息看得我头皮发麻。
所以我的建议是:要么两个都指定,要么用工具链文件统一管理(下一章会讲)。别偷懒只指定一个。
5.7 用SVG理清整个流程
下面这张图,帮你把刚才的操作流程串起来:
这张图其实就说明了一件事:你只需要在CMake配置阶段告诉它“用哪个编译器”,剩下的CMake和Make会自动帮你搞定。源码不用改,CMakeLists.txt不用改,变的只是命令行参数。
5.8 本地编译 vs 交叉编译,对比一下
为了让你更直观地理解区别,我列个表:
| 对比项 | 本地编译 | 交叉编译(本章方式) |
|---|---|---|
| CMake命令 | cmake .. |
cmake .. -DCMAKE_C_COMPILER=arm-... |
| 编译器 | 系统默认gcc | arm-linux-gnueabihf-gcc |
| 生成的可执行文件 | x86架构,可在本机运行 | ARM架构,需在ARM设备上运行 |
| CMakeLists.txt | 无需修改 | 无需修改 |
说白了,区别就一个——编译器不同。其他的一切,CMake都帮你处理好了。
5.9 这种方式有什么局限?
你可能会想:既然这么简单,那为什么还要搞工具链文件?
嗯,问得好。命令行指定编译器的方式,适合快速验证、简单项目。但一旦项目复杂起来,问题就来了:
- 你需要指定很多参数,比如
CMAKE_SYSROOT、CMAKE_FIND_ROOT_PATH等等,命令行会变得又臭又长 - 每次都要手动敲,容易出错
- 团队协作时,每个人都要记住这一长串参数
所以,下一章我们会讲工具链文件——把这些参数写到一个文件里,一劳永逸。
crt0.o 或者 libc.so,那说明你的工具链可能没有配置sysroot。这时候光指定编译器就不够了,需要用到 CMAKE_SYSROOT 参数。这个我们会在后面的章节详细讲。
5.10 小结
这一章的内容其实很简单,但很重要。你亲手完成了第一个交叉编译项目,看到了CMake如何通过命令行参数切换编译器,也验证了生成的可执行文件确实是ARM架构的。
记住这个核心思路:交叉编译 = 换编译器 + 换链接器 + 换库路径。本章我们只做了第一步,后面两步会逐渐展开。
好,去试试吧。写一个Hello World,交叉编译,然后用 file 命令看看——那种“我在x86电脑上造出了ARM程序”的感觉,还是挺爽的。
公众号:蓝海资料掘金营,微信deep3321