重拾 C++:Ubuntu开发环境下的C++学习

备注:还记得第一次学习 C++ 是 2018 年,大学刚入学时,连写一个 Hello World 都要折腾半天。如今已熟练掌握各种 Hello World 写法 (¬‿¬)。毕业后从 Java 转到 C#,如今投身嵌入式开发也快一年了。趁着有空,把 C++ 再系统学一遍!


一、系统与编译器环境信息

1. 查看 Ubuntu 系统版本

css 复制代码
lsb_release -a

输出示例

yaml 复制代码
No LSB modules are available.
Distributor ID:    Ubuntu
Description:       Ubuntu 24.04.3 LTS
Release:           24.04
Codename:          noble

Ubuntu 24.04 LTS 是长期支持版本,稳定性高,非常适合 Linux 学习。


2. 查看 g++ 编译器版本

css 复制代码
g++ --version

输出示例

scss 复制代码
g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0

3. 主流 C++ 编译器对比

编译器 特点 适用场景
GCC (g++) 开源免费、兼容性强、多平台支持,嵌入式主流选择 Ubuntu 默认
Clang 编译速度快、错误提示清晰、基于 LLVM 架构 对编译效率和诊断信息要求高的场景

二、基础实操:编写并运行第一个 C++ 程序

以经典的 Hello World 为例,完整演示从创建文件到运行程序的全流程。

1. 创建项目目录

bash 复制代码
cd ~/Desktop
mkdir learncpp && cd learncpp

2. 使用 Vim 编写 C++ 代码

复制代码
vim hello.cpp

输入以下代码:

c 复制代码
#include <iostream>
using namespace std;
​
int main() {
    cout << "Hello, world!" << endl;
    return 0;
}

3. 编译并运行程序

bash 复制代码
# 编译
g++ hello.cpp -o hello
​
# 运行
./hello

输出

复制代码
Hello, world!

三、深入理解:G++ 编译的四个阶段

C++ 源码 → 可执行文件的过程分为四步:预处理 → 编译 → 汇编 → 链接

示例源文件 hello.cpp:

c 复制代码
#include <iostream>
using namespace std;
​
int main() {
    cout << "Hello, world!" << endl;
    return 0;
}

3.1 阶段一:预处理(Preprocessing)

作用

  • 展开头文件(#include),将指定的头文件内容原封不动地插入到 #include 指令所在的位置。

    • 示例:

      arduino 复制代码
      #include <iostream>

      预处理器会找到标准库中的 stdio.h 文件,并将其全部内容复制粘贴到当前源文件中。

      注意:

      • #include <...> 通常用于系统头文件,搜索路径由编译器预定义;
      • #include "..." 优先在当前目录或用户指定路径中查找,常用于自定义头文件。
  • 替换宏(#define),将宏名替换成其定义的内容,属于纯文本替换。

    • 示例:

      对象式宏(无参数)

      arduino 复制代码
      #define PI 3.1415926

      所有出现 PI 的地方都会被替换为 3.14159

    • 函数式宏(有参数)

      css 复制代码
      #define MAX(a,b) ((a) > (b) ? (a) : (b))

      调用 MAX(x, y) 会被替换为 ((x) > (y) ? (x) : (y)),这里为什么参数都要带括号,感兴趣的可以自己去了解一下。

  • 处理 条件编译(#if, #ifdef, #ifndef, #else, #elif, #endif ,根据预定义的宏或表达式的真假,选择性地包含或排除代码块

    • 常见用于:跨平台兼容、调试开关、防止头文件重复包含
  • 删除注释,字面意思,就是删除所有注释,减少后续编译器负担,预处理后的代码中不再包含任何注释。

    • 注意:

      • 注释被删除后,通常用空格或者换行代替,用来保持代码结构不变,避免两个token被意外拼接。
  • undef:取消已定义的宏

  • pragma:向编译器传递指定指令

  • 行控制:添加行号和文件标识,以便编译时产生报告错误/警告时的行号和文件名

命令

复制代码
g++ -E hello.cpp -o hello.i

查看结果(末尾部分)

bash 复制代码
cat hello.i | tail -n 20
c 复制代码
extern wistream wcin;
  extern wostream wcout;
  extern wostream wcerr;
  extern wostream wclog;
# 82 "/usr/include/c++/13/iostream" 3
  __extension__ __asm (".globl _ZSt21ios_base_library_initv");
​
​
​
}
# 2 "hello.cpp" 2
​
​
# 3 "hello.cpp"
using namespace std;
​
int main(void) {
 cout << "Hello world!"<< endl;
 return 0;
}
​

3.2 阶段二:编译(Compilation → 生成汇编)

作用:将 C++ 代码翻译为目标平台的汇编语言(如 x86-64)。

命令

复制代码
g++ -S hello.cpp -o hello.s

查看汇编代码

bash 复制代码
cat hello.s
perl 复制代码
.file   "hello.cpp"
    .text
#APP
    .globl _ZSt21ios_base_library_initv
    .section    .rodata
.LC0:
    .string "Hello world!"
#NO_APP
    .text
    .globl  main
    .type   main, @function
main:
.LFB1988:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq    .LC0(%rip), %rax
    movq    %rax, %rsi
    leaq    _ZSt4cout(%rip), %rax
    movq    %rax, %rdi
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
    movq    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
    ......

3.3 阶段三:汇编(Assembly → 生成目标文件)

作用 :将 .s 汇编文件转为机器码,生成 .o 目标文件。

命令

r 复制代码
g++ -c hello.cpp -o hello.o

验证文件类型

arduino 复制代码
file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

.o 文件是"可重定位"的,尚未链接库函数。


3.4 阶段四:链接(Linking → 生成可执行文件)

作用

  • 合并 .o 文件与系统库(如 libstdc++, libc )
  • 解析未定义符号(如 std::cout
  • 设置程序入口(_start

命令

bash 复制代码
g++ hello.o -o hello
# 或一步到位:
g++ hello.cpp -o hello

查看动态依赖

复制代码
ldd hello

输出

bash 复制代码
linux-vdso.so.1
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6
libc.so.6      => /lib/x86_64-linux-gnu/libc.so.6
...

所有外部符号均已正确链接。


四、常用 G++ 参数详解(按功能分类)

基础编译控制

参数 说明
-o file 指定输出文件名
-c 仅生成 .o,不链接
-E 仅预处理
-S 仅生成汇编(.s
-v 显示详细编译过程

标准与警告

参数 说明
-std=c++17 / c++20 指定 C++ 标准
-Wall 启用常见警告
-Wextra 启用额外警告
-Werror 警告视为错误
-pedantic 严格遵循 ISO C++ 标准

推荐组合:g++ -std=c++20 -Wall -Wextra -O2


优化选项

参数 说明
-O0 无优化(默认,便于调试)
-O1 / -O2 中等/较高优化
-O3 激进优化(可能增大体积)
-Os 优化代码大小
-g 生成调试信息(供 GDB 使用)

预处理器控制

参数 说明
-DNAME=value 定义宏(如 -DDEBUG=1
-UNAME 取消定义宏
-I/path/to/include 添加头文件搜索路径
-MM 生成 Makefile 依赖关系

链接控制

参数 说明
-L/path/to/lib 添加库搜索路径
-lxxx 链接 libxxx.so(如 -lpthread
-static 静态链接所有库(生成独立可执行文件)
-shared 生成共享库(.so
-Wl,option 向链接器传递参数(如 -Wl,-rpath,/my/lib

调试与分析

参数 说明
-g 生成调试符号
-fno-omit-frame-pointer 保留帧指针,便于性能分析
-save-temps 保留中间文件(.i, .s, .o
-ftime-report 显示各阶段耗时

五、示例

csharp 复制代码
# 1. 预处理(保留所有宏定义)
g++ -E -dD hello.cpp > hello.i
​
# 2. 编译为 Intel 汇编(带注释,-O2 优化)
g++ -S -fverbose-asm -masm=intel -O2 hello.cpp -o hello.s
​
# 3. 汇编为目标文件(可用 as 或 g++)
g++ -c hello.s -o hello.o
​
# 4. 静态链接 libstdc++ 和 libgcc
g++ hello.o -static-libstdc++ -static-libgcc -o hello_static
​
# 5. 查看符号表(确认 cout 是否链接)
nm hello | grep cout

六、调试与分析技巧

1. 查看完整编译过程

复制代码
g++ -v hello.cpp -o hello

显示调用的 cpp → cc1plus → as → ld 等子工具。

2. 保留中间文件

bash 复制代码
g++ -save-temps hello.cpp -o hello
# 自动生成 hello.i, hello.s, hello.o

3. 生成 Makefile 依赖

shell 复制代码
g++ -MM hello.cpp
# 输出:hello.o: hello.cpp /usr/include/c++/.../iostream ...

附录:编译阶段速查表

阶段 输入 输出 命令标志
预处理 .cpp .i -E
编译 .i .s -S
汇编 .s .o -c
链接 .o + 库 可执行文件 (默认)
相关推荐
暗然而日章1 小时前
C++基础:Stanford CS106L学习笔记 4 容器(STL与序列容器)
c++·笔记·学习
kk”2 小时前
C++ AVL树
开发语言·数据结构·c++
西幻凌云2 小时前
认识STLstack容器
c++·stl·适配器·stack·序列式容器
繁华似锦respect2 小时前
C++ 设计模式之观察者模式详细介绍
linux·开发语言·c++·windows·观察者模式·设计模式·visual studio
威桑2 小时前
一个 CMake 项目是否只能有一个 install 目录?
linux·c++·cmake
爪哇部落算法小助手2 小时前
每日两题day61
数据结构·c++·算法
曼巴UE52 小时前
UE5 C++ 多播绑定执行演示
c++·ue5
繁华似锦respect2 小时前
C++ 自定义 String 类
服务器·开发语言·c++·哈希算法·visual studio
phdsky2 小时前
【设计模式】工厂方法模式
c++·设计模式·工厂方法模式