一文搞懂交叉编译
1. 什么是交叉编译?
对于没有做过C/C++跨平台开发或嵌入式开发的同学,可能不太理解交叉编译的概念,那么什么是交叉编译?它有什么作用?什么时候需要用到交叉编译呢?
要解释什么是交叉编译,我们要结合与交叉编译相对的另一个概念本地编译一起来讲解。
1.1. 本地编译
本地编译是指在当前编译平台下,编译出来的程序只能在该平台下运行。也就是说,编译器和目标程序运行在同一个平台上。
这里再理解一下什么是平台?我们在讲C/C++编译相关领域的概念时,平台是指硬件和操作系统的组合,这里的硬件更确切的说就是CPU架构,如:X86、x86_64、ARM、ARM64。因为操作系统可以运行在不同的CPU架构上,但不同的CPU架构采用的指令集是不一样的,如Linux x86_64的程序不能在Linux ARM64上运行。
1.2. 交叉编译
交叉编译可以理解为,在当前编译平台下,编译出来的程序能运行在体系结构不同的另一种目标平台上,但是编译平台本身却不能运行该程序。
比如,我们在x86_64平台的Linux系统上编写程序,并编译成能在ARM64平台的Linux系统上运行的程序,编译得到的程序在x86_64平台的Linux系统上是不能运行的,必须放到ARM64平台的Linux系统上才能正常运行。
1.3. 什么时候需要交叉编译?
正常我们要开发Windows的程序,在Windows上开发并编译就好了;要开发macOS的程序,就买一台苹果电脑,在苹果电脑上开发并编译就好了;这些本地编译就能满足需求了,那什么时候需要交叉编译呢?
我们从交叉编译的概念来猜想一下可能会是什么原因,难倒是“目标平台上不能直接编译出在目标平台上运行的程序”吗?
恭喜你,你猜对了! 之所以需要交叉编译,就是因为:有些目标平台,不能直接编译出能在目标平台上运行的程序。或者能够编译但特别麻烦,没有完整的编译工具链;又或者是目标平台硬件性能比较差,编译速度特别慢。
下面这些目标平台就经常需要用到交叉编译:
- 嵌入式平台: 如无人机、扫地机器人、运动相机等等。嵌入式设备硬件通常是
SoC芯片+Linux系统,或者MCU芯片+RTOS系统,SoC的CPU通常是ARM架构或ARM64架构,内嵌的内存和磁盘资源都非常小,MCU的资源更是少的可怜。所以嵌入式的设备,从硬件性能上就很难支撑自己编译自己需要运行的程序。 - 移动平台: 如
Android手机和iOS手机等,不管是Android系统还是iOS系统,底层都是一个Unix的内核,手机的芯片通常是ARM架构。硬件性能也比较有限,关键是没有完整的编译工具链,所以也需要交叉编译。
可以本地编译的平台

需要交叉编译的平台
- iOS设备
- Android设备
- Harmony设备
- Embedded设备,如树莓派、扫地机/机器人等的SoC系统。
以X86_64的Linux系统上交叉编译ARM64平台的Linux程序为例,其编译流程如下:

2. 如何进行交叉编译?
X86_64的Ubuntu系统,编译ARM64平台的Linux程序。
2.1. 前期准备
2.1.1. 安装 adb
# 安装adb
sudo apt-get install android-tools-adb
# 验证adb是否安装成功
adb --version
2.1.2. 查看目标设备的CPU架构
- 手机(或目标设备)开启USB调试模式。
- 连接手机(或目标设备)到电脑。
- 打开命令行窗口,输入
adb devices查看已经连接的设备。
adb devices
List of devices attached
PQY0221927018610 device
- 通过adb进入设备
# 设备列表只有单个设备时,通过以下命令进入设备
adb shell
# 设备列表有多个设备时,通过以下命令指定设备ID
adb -s <did> shell
- 执行以下命令查看目标设备的CPU架构。
uname -m
aarch64
# 或 (手机可以执行以下命令)
getprop ro.product.cpu.abi
arm64-v8a
我们可以看到ADB连接的手机,CPU架构为ARM64。
2.2. 使用 Ubuntu 官方交叉编译工具链
2.2.1. 下载交叉编译工具链
【举例】:
x86_64的Ubuntu系统下交叉编译aarch64架构下Ubuntu22.04执行的C++程序。
# 更新软件包列表
sudo apt update
# 安装交叉编译工具链
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# 安装必要的库文件(Ubuntu 22.04 aarch64)
sudo apt install libstdc++-12-dev-arm64-cross
2.2.2. 基础编译命令
# 编译简单的 C++ 程序
aarch64-linux-gnu-g++ -o program program.cpp
# 指定 C++ 标准
aarch64-linux-gnu-g++ -std=c++17 -o program program.cpp
# 静态链接
aarch64-linux-gnu-g++ -static -o program program.cpp
【示例】:交叉编译一下HelloWorld程序
// Test.cpp
#include <iostream>
int main()
{
std::cout << "Hello world!" << std::endl;
return 0;
}
本地机器上编译:
aarch64-linux-gnu-g++ -o Test Test.cpp
本地机器上运行:
./Test
aarch64-binfmt-P: Could not open '/lib/ld-linux-aarch64.so.1': No such file or directory
目标设备上运行:
./Test
Hello world!
2.3. 使用 CMake 进行交叉编译
2.3.1. 项目目录结构
./TestProject/
├── CMakeLists.txt
├── Test.cpp
└── toolchain-aarch64.cmake
Test.cpp:
同上面的HelloWorld程序。
toolchain-aarch64.cmake:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# 指定交叉编译器
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
# 指定目标环境
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
CMakeLists.txt:
# cmake version
cmake_minimum_required(VERSION 3.22.0)
# project name
project(TestProject VERSION 1.0.0.0 LANGUAGES CXX)
# add executable
add_executable(${PROJECT_NAME} Test.cpp)
2.3.2. 编译命令
# 配置项目
cmake -S ./ -B ./build -DCMAKE_TOOLCHAIN_FILE=./toolchain-aarch64.cmake
# 编译
cd build/
make
2.3.3. 运行程序
通过ADB或SCP推送到目标设备:
scp ./Test stark@ip:/home/stark/spencer
在目标设备上执行
./TestProject
Hello world!
2.4. 使用 Docker 容器进行交叉编译
2.4.1. 创建 Dockerfile
FROM ubuntu:22.04
RUN apt-get update && \
apt-get install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
build-essential \
cmake \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
2.4.2. 使用 Docker 编译
# 构建 Docker 镜像
docker build -t aarch64-cross-compile .
# 运行容器并编译
docker run -v $(pwd):/workspace -it aarch64-cross-compile \
aarch64-linux-gnu-g++ -o program program.cpp
2.3.4. 比较构建产物
我们可以用file命令来查看编译的可执行文件的文件类型(架构信息)。
X86_64
file ./TestProject
./TestProject: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=53f5025de95889a86814213b7fbdc8d6cb18da63, for GNU/Linux 3.2.0, not stripped
ARM64
file ./TestProject
./TestProject: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=4f7ee6a8cde58d6fa9ef0cd63e812d715a0d619d, for GNU/Linux 3.7.0, not stripped
3. 什么是交叉编译工具链
软件的编译过程由一系列的步骤完成,每一个步骤都有一个对应的工具。这些工具紧密地工作在一起,前一个工具的输出是后一个工具的输入,像一根链条一样,我们称这些工具为工具链。
如Linux系统上的GCC编译工具链:Linux系统上,通常只需要使用gcc就可以完成整个编译过程。但不要被gcc的名字误导,事实上,gcc并不是一个编译器,而是一个驱动程序。在整个的编译过程中,gcc就像一个包工头一样,编译过程中的每一个环节由具体的工具负责。比如编译过程由cc1负责,汇编过程由as负责,链接过程由ld负责。
而用于交叉编译的一整套工具也被称之为交叉编译工具链。如:gcc-aarch64-linux-gnu + g++-aarch64-linux-gnu + cmake。