一、开篇:它们都是干什么的?
在开始写代码之前,我们先搞清楚几个核心角色:
-
g++:GNU C++编译器,负责把C++源代码变成可执行程序。它也可以编译C代码。
-
CMake:跨平台的构建系统生成器------它不直接编译代码,而是根据你的描述生成Makefile或Visual Studio项目文件,再由make或ninja去真正编译。
-
CMakeLists.txt:CMake的"剧本",用CMake语言写的构建配置文件。
-
头文件(.h/.hpp):声明函数、类、变量,相当于"目录"。
-
源文件(.c/.cpp):具体的实现代码,相当于"正文"。
-
库文件(.a静态库/.so动态库):编译好的、可复用的代码集合。
一句话总结:你写代码(源文件+头文件),g++负责编译,CMake负责指挥编译流程,库文件是别人写好供你调用的"预制件"。
二、g++基础:从单文件到多文件
2.1 编译的四个阶段
一个C/C++程序从源代码到可执行文件,要经过四个阶段:
bash
预处理 → 编译 → 汇编 → 链接
可以用g++的选项分别观察每个阶段:
bash
# 1. 预处理:处理#include、#define等,生成.i文件
g++ -E hello.cpp -o hello.i
# 2. 编译:生成汇编代码.s文件
g++ -S hello.i -o hello.s
# 3. 汇编:生成目标文件.o(机器码,但还没链接)
g++ -c hello.s -o hello.o
# 4. 链接:生成最终可执行文件
g++ hello.o -o hello
实际开发中,我们通常一步到位:
bash
g++ hello.cpp -o hello
2.2 常用编译选项
| 选项 | 作用 |
|---|---|
-c |
只编译不链接,生成.o文件 |
-o |
指定输出文件名 |
-I |
指定头文件搜索路径 |
-L |
指定库文件搜索路径 |
-l |
链接指定的库(去掉lib前缀和.so后缀) |
-g |
生成调试信息,供gdb使用 |
-Wall -Wextra |
开启警告 |
-O2 -O3 |
优化等级 |
-std=c++17 |
指定C++标准 |
2.3 多文件编译
假设项目结构如下:
bash
project/
├── main.cpp
├── math.cpp
└── math.h
方法一:一起编译
bash
g++ main.cpp math.cpp -o app
方法二:分开编译(适合大项目,只重新编译修改过的文件)
bash
g++ -c math.cpp # 生成 math.o
g++ -c main.cpp # 生成 main.o
g++ main.o math.o -o app
2.4 链接外部库
假设有一个库 libmylib.so 在 /home/user/lib,头文件在 /home/user/include:
bash
g++ main.cpp -o main -I/home/user/include -L/home/user/lib -lmylib
-lmylib会去查找libmylib.so或libmylib.a。
三、实操一:用g++编译一个简单项目
3.1 创建文件
math.h
cpp
#ifndef MATH_H
#define MATH_H
int add(int a, int b);
#endif
math.cpp
cpp
#include "math.h"
int add(int a, int b) {
return a + b;
}
main.cpp
cpp
#include <iostream>
#include "math.h"
int main() {
std::cout << "3 + 5 = " << add(3, 5) << std::endl;
return 0;
}
3.2 编译运行
bash
# 编译
g++ -c math.cpp -o math.o
g++ -c main.cpp -o main.o
g++ main.o math.o -o app
# 运行
./app
# 输出:3 + 5 = 8

四、静态库 vs 动态库
4.1 概念对比
| 特性 | 静态库(.a) | 动态库(.so) |
|---|---|---|
| 链接时机 | 编译时完整拷入可执行文件 | 运行时动态加载 |
| 文件大小 | 可执行文件较大 | 可执行文件较小 |
| 独立性 | 可独立运行,不依赖库文件 | 运行时需要.so文件存在 |
| 更新 | 更新库需要重新编译整个程序 | 替换.so文件即可,无需重新编译 |
| 内存 | 每个程序各自拷贝一份 | 多个程序共享一份 |
4.2 生成静态库
bash
# 编译目标文件
g++ -c math.cpp -o math.o
# 打包成静态库(ar = archive)
ar rcs libmath.a math.o
4.3 生成动态库
bash
# -fPIC:位置无关代码,动态库必需
g++ -c -fPIC math.cpp -o math.o
# -shared:生成共享库
g++ -shared math.o -o libmath.so
4.4 链接使用
bash
# 链接静态库
g++ main.cpp -L. -lmath -o app_static
# 链接动态库
g++ main.cpp -L. -lmath -o app_shared

五、CMake:告别手写编译命令
当项目有几十个源文件、依赖多个库时,手写g++命令会非常痛苦。CMake就是为了解决这个问题。
5.1 CMake的工作流程
bash
编写CMakeLists.txt → cmake生成Makefile → make编译[reference:24]
5.2 最简CMakeLists.txt
cpp
cmake_minimum_required(VERSION 3.10) # 最低版本要求
project(MyApp VERSION 1.0.0) # 项目名称
set(CMAKE_CXX_STANDARD 17) # 使用C++17[reference:27]
add_executable(app main.cpp math.cpp) # 生成可执行文件[reference:28]
5.3 编译步骤
bash
mkdir build && cd build # 推荐out-of-source构建
cmake .. # 生成Makefile
make # 编译
./app # 运行

5.4 生成库的CMakeLists.txt
cpp
cmake_minimum_required(VERSION 3.10)
project(MathLib)
# 生成静态库
add_library(math STATIC math.cpp) # STATIC = 静态库[reference:29]
# 生成动态库(把STATIC换成SHARED)
# add_library(math SHARED math.cpp) # SHARED = 动态库[reference:30]
# 指定头文件目录,让引用者能找到
target_include_directories(math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
5.5 主程序链接库
cpp
cmake_minimum_required(VERSION 3.10)
project(MyApp)
add_executable(app main.cpp)
target_link_libraries(app math) # 链接math库[reference:31]
5.6 多目录项目结构
实际项目通常这样组织:
bash
myproject/
├── CMakeLists.txt # 根目录
├── src/
│ ├── CMakeLists.txt # 源代码子目录
│ └── main.cpp
├── lib/
│ ├── CMakeLists.txt # 库子目录
│ ├── include/
│ │ └── math.h
│ └── src/
│ └── math.cpp
└── build/ # 编译输出目录(不提交git)
根目录CMakeLists.txt:
cpp
cmake_minimum_required(VERSION 3.10)
project(MyProject)
add_subdirectory(lib) # 先编译库
add_subdirectory(src) # 再编译主程序
六、实操二:ARM交叉编译
你现在有ARM环境,我们来看看怎么用g++和CMake为ARM编译程序。
6.1 什么是交叉编译?
交叉编译就是在一种平台上编译出能在另一种架构上运行的程序。比如在x86的PC上编译出ARM开发板上运行的程序。
6.2 安装ARM交叉编译工具链
在Ubuntu/Debian上:
bash
sudo apt update
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf[reference:36]
验证安装:
bash
arm-linux-gnueabihf-g++ --version
6.3 直接用g++交叉编译
bash
# 编译ARM版本
arm-linux-gnueabihf-g++ main.cpp math.cpp -o app_arm
# 查看文件架构
file app_arm
# 输出示例:ELF 32-bit LSB executable, ARM, EABI5 version 1 ...[reference:37]
对比本地编译:
bash
g++ main.cpp math.cpp -o app_x86
file app_x86
# 输出:ELF 64-bit LSB executable, x86-64 ...

6.4 用CMake进行ARM交叉编译
CMake交叉编译的推荐方式是使用工具链文件(toolchain file)。
创建 toolchain.cmake:
cpp
# 设置目标系统
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)[reference:39]
# 指定交叉编译器[reference:40]
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
# 可选:指定根文件系统路径
# set(CMAKE_SYSROOT /path/to/arm/rootfs)
编译:
bash
mkdir build-arm && cd build-arm
cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake
make
file app
# 输出:ELF 32-bit LSB executable, ARM ...

6.5 两种方式的对比:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 交叉编译(x86→ARM) | 速度快、资源多 | 需要配置工具链 |
| 原生编译(在ARM上) | 无需配置、直接运行 | ARM设备性能可能较弱 |
七、C与C++的关键区别(编译层面)
7.1 编译器不同
-
C语言用 gcc(GNU C Compiler)
-
C++用 g++(GNU C++ Compiler)
-
但g++也可以编译C代码
7.2 函数名修饰(Name Mangling)------混合编程的关键
这是C和C++在编译层面最本质的区别:
-
C编译器 :函数名直接作为符号名,比如
add就是add -
C++编译器 :支持函数重载,所以会把参数类型编进符号名,比如
add(int,int)可能变成_Z3addii
这就导致了一个问题 :用C编译器生成的库(符号名是 add),在C++中链接时去找 _Z3addii,找不到就会报 undefined reference。
7.3 解决方案:extern "C"
cpp
// 在C++代码中声明C库的函数
extern "C" {
#include "c_library.h"
}
或者头文件中这样写:
cpp
#ifdef __cplusplus
extern "C" {
#endif
// ... 函数声明 ...
#ifdef __cplusplus
}
#endif
这样C++编译器就会用C的方式处理这些函数名,保证能正确链接。
八、总结对比
| 概念 | 是什么 | 后缀 | 谁生成/使用 |
|---|---|---|---|
| 源文件 | 源代码 | .c / .cpp | 你写 |
| 头文件 | 声明 | .h / .hpp | 你写,被#include |
| 目标文件 | 编译后的机器码 | .o | g++ -c 生成 |
| 静态库 | 目标文件的归档 | .a | ar 打包,链接时完整拷入 |
| 动态库 | 运行时加载的共享代码 | .so | g++ -shared 生成 |
| 可执行文件 | 最终程序 | 无后缀(Linux) | g++ 链接生成 |
| CMakeLists.txt | 构建脚本 | .txt | 你写,cmake读取 |
| Makefile | 构建指令 | 无后缀 | cmake生成,make执行 |
九、快速参考卡片
g++常用命令:
bash
g++ -c file.cpp -o file.o # 只编译
g++ file1.o file2.o -o app # 链接
g++ -I/path/to/include main.cpp -o app # 指定头文件
g++ -L/path/to/lib -lmylib main.cpp -o app # 链接库
CMake常用命令:
bash
add_executable(app main.cpp) # 可执行文件
add_library(mylib STATIC math.cpp) # 静态库
add_library(mylib SHARED math.cpp) # 动态库
target_link_libraries(app mylib) # 链接库
target_include_directories(app PUBLIC include) # 头文件目录
CMake编译流程:
bash
mkdir build && cd build
cmake ..
make
希望这篇文章能帮你彻底理清g++、CMake以及各种文件类型的关系。动手跟着实操走一遍,比看十遍文章都管用!