C/C++ Linux 开发教程(GCC + CMake) / 跨平台与交叉编译
跨平台与交叉编译
交叉编译是在一个平台(host)上编译出另一个平台(target)可执行代码的过程。这在嵌入式开发、ARM 服务器、WebAssembly 等场景中至关重要。
交叉编译概念
基本术语
| 术语 | 说明 | 示例 |
|---|
| Host | 运行编译器的平台 | x86_64 Linux |
| Target | 生成代码运行的平台 | ARM64 Linux |
| Build | 编译编译器本身的平台 | (一般与 Host 相同) |
| Sysroot | 目标系统的根文件系统 | /opt/sysroot |
| Toolchain | 交叉编译工具链 | aarch64-linux-gnu-gcc |
为什么需要交叉编译
- 目标平台资源有限:嵌入式设备无法运行编译器
- 编译速度:在高性能 x86 机器上编译比在 ARM 开发板上快得多
- CI/CD:一套构建机器生成多平台产物
- WebAssembly:在浏览器中运行 C/C++ 代码
CMake 工具链文件结构
# toolchain-aarch64.cmake
# AArch64 (ARM64) 交叉编译工具链文件
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++)
# 指定 sysroot(目标系统的根文件系统)
set(CMAKE_SYSROOT /opt/aarch64-sysroot)
# 查找库和头文件的路径
set(CMAKE_FIND_ROOT_PATH /opt/aarch64-sysroot)
# 查找策略
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # 程序在 host 上运行
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # 库只在 sysroot 中找
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # 头文件只在 sysroot 中找
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) # CMake 包只在 sysroot 中找
# 编译标志
set(CMAKE_C_FLAGS_INIT "-march=armv8-a")
set(CMAKE_CXX_FLAGS_INIT "-march=armv8-a")
使用工具链文件
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=toolchain-aarch64.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
# 验证产物
file build/app
# build/app: ELF 64-bit LSB executable, ARM aarch64...
常用交叉编译工具链
ARM 工具链安装
# Debian/Ubuntu
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
# Arch Linux
sudo pacman -S aarch64-linux-gnu-gcc arm-linux-gnueabihf-gcc
# 验证安装
aarch64-linux-gnu-gcc --version
各平台工具链文件
# toolchain-arm32.cmake — ARM 32 位
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_C_FLAGS_INIT "-march=armv7-a -mfpu=neon")
set(CMAKE_CXX_FLAGS_INIT "-march=armv7-a -mfpu=neon")
# toolchain-riscv64.cmake — RISC-V 64 位
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR riscv64)
set(CMAKE_C_COMPILER riscv64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER riscv64-linux-gnu-g++)
set(CMAKE_C_FLAGS_INIT "-march=rv64gc")
set(CMAKE_CXX_FLAGS_INIT "-march=rv64gc")
# toolchain-mingw64.cmake — Windows 交叉编译
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Clang 交叉编译
# toolchain-clang-aarch64.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)
# Clang 使用 --target 指定目标
set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu)
set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-gnu)
set(CMAKE_SYSROOT /opt/aarch64-sysroot)
set(CMAKE_C_FLAGS_INIT "--gcc-toolchain=/usr")
set(CMAKE_CXX_FLAGS_INIT "--gcc-toolchain=/usr")
Sysroot 配置
创建 Sysroot
# 方法 1: 从目标设备复制
# 在树莓派上运行
rsync -avz --exclude={'/dev/*','/proc/*','/sys/*','/tmp/*','/run/*','/mnt/*','/media/*','/lost+found'} \
pi@raspberrypi:/ /opt/rpi-sysroot/
# 方法 2: 使用 debootstrap 创建
sudo debootstrap --arch=arm64 --foreign jammy /opt/aarch64-sysroot http://ports.ubuntu.com
# 方法 3: 使用 Docker 提取
docker create --name sysroot --platform linux/arm64 ubuntu:22.04
docker cp sysroot:/ /opt/aarch64-sysroot
docker rm sysroot
修复 Sysroot 符号链接
# Sysroot 中的绝对符号链接会导致问题
# 需要转换为相对链接
#!/bin/bash
# fix_symlinks.sh
SYSROOT="/opt/aarch64-sysroot"
find "$SYSROOT" -type l | while read link; do
target=$(readlink -f "$link")
# 检查是否指向 sysroot 内部
if [[ "$target" == "$SYSROOT"* ]]; then
# 计算相对路径
rel=$(realpath --relative-to="$(dirname $link)" "$target")
ln -sf "$rel" "$link"
fi
done
PKG_CONFIG 与 Sysroot
# 设置交叉编译的 pkg-config
export PKG_CONFIG_PATH=/opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=/opt/aarch64-sysroot
# 或者创建包装脚本
cat > aarch64-pkg-config << 'EOF'
#!/bin/bash
export PKG_CONFIG_PATH=/opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=/opt/aarch64-sysroot
exec pkg-config "$@"
EOF
chmod +x aarch64-pkg-config
交叉编译第三方库
使用 CMake 编译第三方库
# 编译 zlib
wget https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz
tar xzf zlib-1.3.1.tar.gz
cd zlib-1.3.1
# 使用工具链文件
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=../toolchain-aarch64.cmake \
-DCMAKE_INSTALL_PREFIX=/opt/aarch64-sysroot/usr/local
cmake --build build
cmake --install build
使用 Conan 交叉编译
# conanfile.py
[settings]
os=Linux
os_build=Linux
arch=armv8
arch_build=x86_64
compiler=gcc
compiler.version=12
compiler.libcxx=libstdc++11
build_type=Release
[buildenv]
CC=aarch64-linux-gnu-gcc
CXX=aarch64-linux-gnu-g++
conan install . -pr:a profiles/aarch64-linux --output-folder=build
cmake -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
Docker 交叉编译环境
Dockerfile 示例
# Dockerfile.cross-aarch64
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
build-essential \
cmake \
ninja-build \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
qemu-user-static \
&& rm -rf /var/lib/apt/lists/*
# 可选: 安装 sysroot
COPY sysroot/ /opt/aarch64-sysroot/
WORKDIR /app
COPY . .
# 使用工具链文件
RUN cmake -B build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-aarch64.cmake \
-DCMAKE_BUILD_TYPE=Release \
&& cmake --build build
# 使用 QEMU 测试(可选)
RUN qemu-aarch64-static -L /opt/aarch64-sysroot ./build/app --version
多平台 Docker 构建
# docker-compose.yml
version: '3.8'
services:
build-amd64:
build:
context: .
dockerfile: Dockerfile
args:
PLATFORM: linux/amd64
volumes:
- ./output/amd64:/output
build-arm64:
build:
context: .
dockerfile: Dockerfile
args:
PLATFORM: linux/arm64
volumes:
- ./output/arm64:/output
Docker Buildx 多平台
# 创建 builder
docker buildx create --name cross-builder --use
# 构建多平台镜像
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
-t myapp:latest \
--push .
多平台构建脚本
构建矩阵脚本
#!/bin/bash
# build_all.sh — 构建多平台版本
set -e
PLATFORMS=(
"x86_64-linux:cmake/toolchain-x86_64.cmake"
"aarch64-linux:cmake/toolchain-aarch64.cmake"
"arm-linux:cmake/toolchain-arm32.cmake"
"mingw64:cmake/toolchain-mingw64.cmake"
)
for platform_info in "${PLATFORMS[@]}"; do
IFS=':' read -r platform toolchain <<< "$platform_info"
echo "=== Building for ${platform} ==="
build_dir="build-${platform}"
output_dir="output/${platform}"
cmake -B "${build_dir}" \
-DCMAKE_TOOLCHAIN_FILE="${toolchain}" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${output_dir}"
cmake --build "${build_dir}" --parallel $(nproc)
cmake --install "${build_dir}"
echo "=== ${platform} build complete ==="
done
# 汇总产物
echo ""
echo "Build artifacts:"
for platform_info in "${PLATFORMS[@]}"; do
IFS=':' read -r platform toolchain <<< "$platform_info"
echo " ${platform}:"
ls -la output/${platform}/bin/ 2>/dev/null || echo " (no binaries)"
done
GitHub Actions 多平台构建
# .github/workflows/multi-platform.yml
name: Multi-Platform Build
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
include:
- platform: x86_64-linux
toolchain: cmake/toolchain-x86_64.cmake
runner: ubuntu-latest
- platform: aarch64-linux
toolchain: cmake/toolchain-aarch64.cmake
runner: ubuntu-latest
- platform: mingw64
toolchain: cmake/toolchain-mingw64.cmake
runner: ubuntu-latest
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- name: Install cross-compiler
run: |
sudo apt-get update
if [ "${{ matrix.platform }}" = "aarch64-linux" ]; then
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" = "mingw64" ]; then
sudo apt-get install -y gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64
fi
- name: Build
run: |
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=${{ matrix.toolchain }} \
-DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.platform }}
path: build/app*
WebAssembly 编译(Emscripten)
Emscripten 安装
# 安装 Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
# 验证
emcc --version
CMake 工具链文件
# toolchain-emscripten.cmake
set(CMAKE_SYSTEM_NAME Emscripten)
set(CMAKE_SYSTEM_PROCESSOR x86)
# Emscripten 工具链路径
set(EMSDK_PATH "$ENV{EMSDK}")
if(NOT EMSDK_PATH)
message(FATAL_ERROR "EMSDK environment variable not set")
endif()
set(CMAKE_C_COMPILER emcc)
set(CMAKE_CXX_COMPILER em++)
set(CMAKE_AR emar)
set(CMAKE_RANLIB emranlib)
# Emscripten 特定设置
set(CMAKE_C_FLAGS_INIT "-s WASM=1")
set(CMAKE_CXX_FLAGS_INIT "-s WASM=1")
# 禁用在 sysroot 中查找
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
编译 WebAssembly
# CMakeLists.txt — 适配 Emscripten
cmake_minimum_required(VERSION 3.10)
project(MyWasmApp C)
add_executable(app main.c)
if(EMSCRIPTEN)
# 设置 Emscripten 链接选项
set_target_properties(app PROPERTIES
SUFFIX ".html" # 生成 HTML 包装器
LINK_FLAGS "-s WASM=1 -s EXPORTED_FUNCTIONS='[_main,_add]' -s EXPORTED_RUNTIME_METHODS='[\"ccall\",\"cwrap\"]'"
)
endif()
# 使用 Emscripten 工具链
source /path/to/emsdk/emsdk_env.sh
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
# 生成的文件:
# build/app.html — HTML 包装器
# build/app.js — JavaScript 加载代码
# build/app.wasm — WebAssembly 二进制
在 HTML 中使用
<!DOCTYPE html>
<html>
<head>
<title>WebAssembly Demo</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var Module = {
onRuntimeInitialized: function() {
// 调用导出的 C 函数
var result = Module._add(3, 4);
console.log("3 + 4 = " + result);
// 使用 ccall 包装器
var result2 = Module.ccall('add', 'number',
['number', 'number'], [5, 6]);
console.log("5 + 6 = " + result2);
}
};
</script>
<script src="app.js"></script>
</body>
</html>
交叉编译调试策略
QEMU 用户模式模拟
# 安装 QEMU 用户模式
sudo apt install qemu-user-static
# 直接运行 ARM 二进制(需要注册 binfmt)
sudo apt install binfmt-support
# 注册后可以直接运行:
./build-arm/app # 自动通过 QEMU 运行
# 指定 sysroot 运行
qemu-aarch64-static -L /opt/aarch64-sysroot ./build-arm/app
# 调试
qemu-aarch64-static -L /opt/aarch64-sysroot -g 1234 ./build-arm/app &
gdb-multiarch -ex "target remote :1234" -ex "set architecture aarch64" ./build-arm/app
远程调试
# 在目标设备上运行 gdbserver
gdbserver :1234 ./app
# 在开发机上连接
gdb-multiarch ./build-arm/app
(gdb) target remote 192.168.1.100:1234
(gdb) break main
(gdb) continue
调试工具对比
| 方案 | 优点 | 缺点 |
|---|
| QEMU 用户模式 | 无需目标设备 | 性能差异 |
| gdbserver 远程调试 | 真实环境 | 需要目标设备 |
| Docker + QEMU | 隔离环境 | 配置复杂 |
| 硬件调试器(JTAG) | 最准确 | 需要专用硬件 |
实际案例
案例 1:树莓派交叉编译
# toolchain-rpi.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR armv7l)
# 树莓派专用工具链(来自官方 SDK)
set(CMAKE_C_COMPILER /opt/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /opt/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++)
# 树莓派 sysroot(从设备复制)
set(CMAKE_SYSROOT /opt/rpi-sysroot)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
# 树莓派特定标志
set(CMAKE_C_FLAGS_INIT "-march=armv7-a -mfpu=neon -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS_INIT "-march=armv7-a -mfpu=neon -mfloat-abi=hard")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# 构建
cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchain-rpi.cmake
cmake --build build --parallel
# 部署到树莓派
scp build/app pi@raspberrypi:/home/pi/
# 在树莓派上测试
ssh pi@raspberrypi './app'
案例 2:嵌入式 Linux(Yocto SDK)
# 安装 Yocto SDK
source /opt/poky/3.4/environment-setup-cortexa72-poky-linux
# SDK 会设置以下环境变量:
# CC, CXX, CFLAGS, LDFLAGS, PKG_CONFIG_SYSROOT_DIR 等
# 直接使用环境变量编译
cmake -B build \
-DCMAKE_C_COMPILER="$CC" \
-DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_C_FLAGS="$CFLAGS" \
-DCMAKE_CXX_FLAGS="$CXXFLAGS" \
-DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS"
案例 3:Android NDK
# toolchain-android.cmake
set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# Android NDK 路径
set(ANDROID_NDK $ENV{ANDROID_NDK_HOME})
set(CMAKE_ANDROID_NDK ${ANDROID_NDK})
# API 级别
set(CMAKE_ANDROID_API 21)
# ABI
set(CMAKE_ANDROID_ARCH_ABI arm64-v8a)
# 工具链
set(CMAKE_C_COMPILER ${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang)
set(CMAKE_CXX_COMPILER ${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++)
⚠️ 注意点
- 不要用 CMAKE_SYSTEM_PROCESSOR 硬编码:使用变量或工具链文件
- sysroot 绝对符号链接:必须修复,否则链接会失败
- host 与 target 工具区分:构建工具(如代码生成器)用 host 的,编译结果用 target 的
- 测试:交叉编译后必须在目标平台或模拟器上测试
- 字节序:大端/小端不匹配会导致隐蔽 Bug
- 库路径:
-L 和 CMAKE_FIND_ROOT_PATH 要正确设置
💡 提示
- file 命令验证:用
file 检查产物的架构 - readelf 检查:用
readelf -h 查看 ELF 头信息 - CMake 打印变量:
cmake -B build -LAH 查看所有变量 - Clang 更适合交叉编译:只需
--target 即可切换目标 - ccache 加速:设置
CMAKE_C_COMPILER_LAUNCHER=ccache - CMake 预设:用 CMakePresets.json 管理多平台配置
扩展阅读