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

能看到版本信息,说明工具链已经就绪。

小提示: 如果你用的是其他架构,比如aarch64(ARM 64位),那就装 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架构的可执行文件。

核心要点: 交叉编译的本质,就是换掉编译器。CMake本身不关心你用什么架构,它只负责调用你指定的编译器。你给它x86的gcc,它就编译x86的程序;你给它ARM的gcc,它就编译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命令行交叉编译流程 main.c + CMakeLists.txt cmake .. -DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc CMake检测到ARM编译器,生成ARM架构的构建系统 make → 调用ARM编译器编译 hello_cross (ARM ELF可执行文件) file 命令验证:ELF 32-bit LSB ARM

这张图其实就说明了一件事:你只需要在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_SYSROOTCMAKE_FIND_ROOT_PATH 等等,命令行会变得又臭又长
  • 每次都要手动敲,容易出错
  • 团队协作时,每个人都要记住这一长串参数

所以,下一章我们会讲工具链文件——把这些参数写到一个文件里,一劳永逸。

注意: 如果你在交叉编译时遇到链接错误,比如找不到 crt0.o 或者 libc.so,那说明你的工具链可能没有配置sysroot。这时候光指定编译器就不够了,需要用到 CMAKE_SYSROOT 参数。这个我们会在后面的章节详细讲。

5.10 小结

这一章的内容其实很简单,但很重要。你亲手完成了第一个交叉编译项目,看到了CMake如何通过命令行参数切换编译器,也验证了生成的可执行文件确实是ARM架构的。

记住这个核心思路:交叉编译 = 换编译器 + 换链接器 + 换库路径。本章我们只做了第一步,后面两步会逐渐展开。

好,去试试吧。写一个Hello World,交叉编译,然后用 file 命令看看——那种“我在x86电脑上造出了ARM程序”的感觉,还是挺爽的。


公众号:蓝海资料掘金营,微信deep3321