《C/C++编译全家桶:g++四阶段、.a与.so区别、CMake构建、ARM交叉编译,这篇全讲透了》

一、开篇:它们都是干什么的?

在开始写代码之前,我们先搞清楚几个核心角色:

  • 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.solibmylib.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以及各种文件类型的关系。动手跟着实操走一遍,比看十遍文章都管用!

相关推荐
冰心孤城13 小时前
C++ 与 C#混合编程 示例 (基于VS)
java·c++·c#
kisloy14 小时前
【爬虫入门第5讲】Python爬虫文本解析详解
开发语言·爬虫·python
浩浩kids14 小时前
C•exercises
c语言
春生野草15 小时前
个人笔记——C语言字符串、树
c语言·开发语言·笔记
风中芦苇啊15 小时前
Java EasyExcel 导入通用工具类:自定义注解映射字段 + 反射机制
java·开发语言
PieroPc15 小时前
Python 写的 Windows凭据添加工具 第二版 解决Win11 23H2, 25H2,26H1,共享文件和打印 一部份问题,不是全部!
开发语言·windows·python
家有娇妻张兔兔15 小时前
Java 对接 PLC 主流型号最合适的方案:Apache PLC4X 实战指南
java·开发语言·plc·modbus·数据缓存·西门子s7·apache plc4x
The_Ticker16 小时前
大宗商品行情API接入教程
开发语言·python·websocket·程序人生·区块链
Infedium16 小时前
Qt嵌入式开发核心亮点:线程安全硬件交互技术
开发语言·qt