一、工欲善其事,必先利其器
说实话,我见过太多人一上来就撸代码,结果编译报错搞半天,内存泄漏查一宿。嗯,这其实挺亏的。开发环境就像你的工作台——工具顺手了,效率自然就上来了。
这一章,咱们就把 Linux 下的 C++ 开发环境彻底捋一遍。从编译器到构建工具,从调试器到内存检测,我会把我在项目中踩过的坑、积累的经验,一股脑儿倒出来。
二、Linux 开发环境配置
我个人习惯用 Ubuntu 20.04 LTS 或 22.04 LTS。为什么?因为社区活跃,遇到问题一搜就有答案。你想想看,要是用个冷门发行版,编译个库都得自己折腾半天,何必呢?
2.1 基础环境安装
拿到一台新机器,第一件事就是更新包管理器,然后装上基础工具链:
# 更新软件源
sudo apt update && sudo apt upgrade -y
# 安装基础开发工具
sudo apt install build-essential
sudo apt install cmake
sudo apt install gdb
sudo apt install valgrind
# 安装网络库依赖(后面章节会用到)
sudo apt install libboost-all-dev
sudo apt install libssl-dev
gcc --version 确认版本。我遇到过好几次装完发现 gcc 没装全,编译时提示缺少 cc1plus,那叫一个尴尬。
2.2 编译器版本管理
有时候项目需要特定版本的 GCC。比如老项目可能只能用 GCC 7,新项目想用 GCC 11。怎么办?
我推荐用 update-alternatives 来管理多版本:
# 安装多个 GCC 版本
sudo apt install gcc-9 g++-9 gcc-11 g++-11
# 配置优先级
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 90
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 110
# 切换版本
sudo update-alternatives --config gcc
三、GCC/G++ 编译器
GCC 是 Linux 下 C++ 编译的事实标准。说白了,它就是把你写的 C++ 代码变成机器码的翻译官。
3.1 编译流程与常用参数
一个完整的编译流程分为四步:预处理 → 编译 → 汇编 → 链接。我平时最常用的参数是这些:
| 参数 | 作用 | 我的使用场景 |
|---|---|---|
-O2 |
开启优化 | 发布版本必加 |
-g |
生成调试信息 | 调试时必加,否则 GDB 看不到源码 |
-Wall -Wextra |
开启所有警告 | 我习惯加上,能提前发现很多潜在问题 |
-std=c++17 |
指定 C++ 标准 | 新项目我一般用 C++17 或 C++20 |
-lpthread |
链接 pthread 库 | 多线程程序必备 |
举个例子,编译一个简单的网络程序:
g++ -std=c++17 -O2 -g -Wall -Wextra -o server main.cpp network.cpp -lpthread
-O0 -g,发布版本用 -O2 -g(保留调试符号方便线上排查)。别问我为什么发布版本也要加 -g,等你线上 core dump 了却看不到堆栈信息,你就懂了。
3.2 静态库与动态库
项目中经常需要把公共代码打包成库。我一般这样操作:
# 编译成目标文件
g++ -c -fPIC network.cpp -o network.o
# 打包成静态库
ar rcs libnetwork.a network.o
# 打包成动态库
g++ -shared -fPIC -o libnetwork.so network.o
# 链接静态库
g++ main.cpp -L. -lnetwork -o server
# 链接动态库
g++ main.cpp -L. -lnetwork -o server
# 运行时需要指定库路径:export LD_LIBRARY_PATH=./
四、CMake 构建工具
说实话,早期我写 Makefile 写到吐。尤其是项目大了之后,依赖关系一多,Makefile 就成了灾难。后来换了 CMake,世界清净了。
4.1 一个标准的 CMakeLists.txt
这是我个人习惯的项目结构:
project/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ ├── network.cpp
│ └── network.h
├── lib/
│ └── (第三方库)
└── build/
对应的 CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(MyServer VERSION 1.0.0 LANGUAGES CXX)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 开启调试信息(Debug 模式自动加 -g)
set(CMAKE_BUILD_TYPE Debug)
# 添加可执行文件
add_executable(server
src/main.cpp
src/network.cpp
)
# 链接线程库
target_link_libraries(server pthread)
# 添加头文件路径
target_include_directories(server PRIVATE src)
构建命令:
cd build
cmake ..
make -j$(nproc) # -j 并行编译,nproc 获取 CPU 核心数
4.2 多目录项目
项目大了之后,我习惯按模块分目录:
# 根 CMakeLists.txt
add_subdirectory(src/network)
add_subdirectory(src/http)
add_subdirectory(src/server)
# src/network/CMakeLists.txt
add_library(network
tcp_connection.cpp
tcp_server.cpp
)
target_include_directories(network PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
五、GDB 调试器
调试是每个 C++ 程序员的必修课。GDB 就是那个帮你揪出 bug 的利器。
5.1 基本调试流程
我常用的调试套路:
# 启动调试
gdb ./server
# 设置断点
(gdb) break main
(gdb) break network.cpp:42 # 在文件第42行设断点
(gdb) break MyClass::handle # 在成员函数设断点
# 运行程序
(gdb) run
# 查看变量
(gdb) print request
(gdb) print buffer[0]@10 # 打印数组前10个元素
# 单步执行
(gdb) next # 下一行(不进入函数)
(gdb) step # 下一步(进入函数)
(gdb) finish # 执行到函数返回
# 查看调用栈
(gdb) backtrace
(gdb) frame 2 # 切换到第2层栈帧
# 条件断点(超级实用)
(gdb) break network.cpp:100 if fd > 1024
thread apply all bt 可以打印所有线程的调用栈。我曾经靠这个命令,在 20 个线程里找到了那个死锁的线程——它卡在一个锁上等了 30 秒。
5.2 调试 Core Dump
程序崩溃后,core dump 文件就是案发现场的照片。设置方法:
# 设置 core dump 文件大小不限
ulimit -c unlimited
# 运行程序,崩溃后生成 core 文件
./server
# 用 GDB 分析 core 文件
gdb ./server core
# 查看崩溃位置
(gdb) bt
(gdb) info locals
(gdb) frame 0 # 查看崩溃时的栈帧
/etc/security/limits.conf 里配好。
六、Valgrind 内存检测
内存泄漏是 C++ 的老大难问题。Valgrind 就是那个帮你抓内存问题的侦探。
6.1 检测内存泄漏
# 编译时加 -g 保留调试信息
g++ -g -O0 -o server main.cpp network.cpp
# 运行 Valgrind
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
./server
输出示例:
==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 5
==12345== at 0x4C2B0E0: operator new(unsigned long)
==12345== by 0x400A5F: create_connection() (network.cpp:42)
==12345== by 0x400B12: main (main.cpp:25)
看到这个输出,你就知道:第 42 行 new 出来的对象没 delete。修复起来就很简单了。
6.2 检测越界访问
valgrind --tool=memcheck ./server
Valgrind 能检测出:
- 使用未初始化的内存
- 读写已经释放的内存
- 数组越界
- 内存泄漏
6.3 性能分析工具
除了 Valgrind,我还常用 perf 做性能分析:
# 采样分析
perf record -g ./server
perf report
# 查看热点函数
perf top
这两个工具配合使用,基本能覆盖 90% 的性能问题排查场景。
七、总结
这一章咱们把 Linux 下的 C++ 开发环境捋了一遍。从 GCC 编译到 CMake 构建,从 GDB 调试到 Valgrind 检测,每个工具都有它的用武之地。
记住一句话:工具是死的,人是活的。别死记硬背命令,多用、多练、多踩坑,慢慢就熟练了。我刚开始用 GDB 时也记不住命令,后来写了个小抄贴在显示器边上,用了两周就全记住了。
下一章,咱们开始真正写网络程序。到时候这些工具都会派上用场——相信我,你会感谢自己提前配好了环境的。