笔记目录

Phase 1:C/C++ + Linux 基础地基

  1. 内存与指针

  2. struct/class 与对象生命周期

  3. RAII 与资源管理

  4. STL 容器

  5. 多线程同步

  6. socket 网络基础

  7. Linux 调试与构建工具

Phase 2:音视频 + 传输协议

  1. 图像/视频基础

  2. H.264/H.265

  3. 时间戳与同步

  4. RTP/RTSP/RTMP/HLS/WebRTC

  5. FFmpeg / GStreamer

Phase 3:嵌入式平台链路

  1. 交叉编译

  2. SDK 与媒体 pipeline

  3. 板端部署

  4. 性能、日志、排障

  5. 端到端链路追踪


第 1 周:指针、内存、生命周期

目标:

  • 知道栈、堆、全局区大概怎么回事

  • 知道指针和变量到底是什么关系

  • 知道悬空指针、野指针、重复释放为什么危险

  • 能用 gdb 看地址和内存变化

第 2 周:struct/class、构造/析构、拷贝/移动

目标:

  • 搞清对象何时创建、何时销毁

  • 能看懂值传递、引用传递

  • 理解浅拷贝/深拷贝

  • 知道为什么 C++ 要有移动语义

第 3 周:STL 与 RAII

目标:

  • vector / map / unordered_map / queue

  • string / pair

  • unique_ptr / shared_ptr

  • 资源管理为什么要 RAII

第 4 周:多线程与同步

目标:

  • 线程创建

  • mutex

  • condition_variable

  • 生产者-消费者模型

第 5 周:Linux 网络编程基础

目标:

  • socket 是什么

  • TCP 服务器最小模型

  • client/server 通信过程

  • 阻塞/非阻塞

第 6 周:Linux 工具链与排障

目标:

  • gdb

  • strace

  • lsof

  • tcpdump

  • Makefile

  • CMake

  • core dump 基础排查

第一课:指针、内存布局、生命周期

学一个核心:

“变量到底存在哪里,指针到底指向什么”

这是后面学 socket、线程、音视频 buffer、板端 SDK 的底层基础。

1. 先建立最小认知模型

先记这句话:

变量 = 一块内存里的数据

指针 = 这块内存的地址

比如:

int a = 10;
int* p = &a;

含义不是“p 等于 a”,而是:

  • a 是一个 int

  • a 的值是 10

  • &a 是 a 的地址

  • p 保存这个地址

  • *p 表示去这个地址里取值

所以:

  • p 看的是“位置”

  • *p 看的是“位置里的内容”

2. 内存区

(1)栈 stack

特点:

  • 函数里的局部变量通常在栈上

  • 生命周期跟函数作用域绑定

  • 函数返回,这块局部内存通常就失效了

例子:

void f() {
    int x = 5;
}

x 一般就是栈变量。

(2)堆 heap

特点:

  • 手动申请

  • 手动释放

  • 生命周期不由作用域自动控制

例子:

int* p = new int(5);
delete p;

(3)静态/全局区

特点:

  • 程序启动就存在

  • 程序结束才释放

例子:

int g_value = 100;
static int s_value = 200;

3. 为什么生命周期是核心

看这个错误例子:

int* bad_func() {
    int x = 10;
    return &x;
}

为什么错?

因为 x 是局部变量,函数结束后它的生命周期结束了。

你返回它的地址,外面拿到的是一个失效地址。

这就是典型的:

悬空指针 dangling pointer

这类 bug 在嵌入式和服务端都很难查,因为:

  • 编译可能不报错

  • 运行不一定马上崩

  • 但行为已经不可靠

4. 第一个实验:打印地址,建立“内存感”

把下面代码保存为 lesson1_memory.cpp:

#include <iostream>

int g_value = 100;

void test_stack() {
    int a = 10;
    int b = 20;

    std::cout << "[stack] a value = " << a << ", address = " << &a << std::endl;
    std::cout << "[stack] b value = " << b << ", address = " << &b << std::endl;
}

void test_heap() {
    int* p = new int(30);

    std::cout << "[heap] p value(address stored in p) = " << p << std::endl;
    std::cout << "[heap] *p = " << *p << std::endl;
    std::cout << "[heap] &p(address of pointer variable itself) = " << &p << std::endl;

    delete p;
    p = nullptr;
}

void test_global() {
    static int s_value = 200;

    std::cout << "[global] g_value = " << g_value << ", address = " << &g_value << std::endl;
    std::cout << "[static] s_value = " << s_value << ", address = " << &s_value << std::endl;
}

int main() {
    test_stack();
    test_heap();
    test_global();
    return 0;
}

编译:

g++ -std=c++17 -g lesson1_memory.cpp -o lesson1_memory

运行:

./lesson1_memory

观察:

  • a、b 的地址彼此很近

  • p 的值本身是一个地址

  • &p 和 p 不是一回事

  • 全局/static 变量地址通常和局部变量区域风格不同

5. 第二个实验:错误生命周期

保存为 lesson1_dangling.cpp:

#include <iostream>

int* bad_func() {
    int x = 123;
    return &x;
}

int main() {
    int* p = bad_func();

    std::cout << "p = " << p << std::endl;
    std::cout << "*p = " << *p << std::endl;  // 未定义行为

    return 0;
}

编译:

g++ -std=c++17 -g -Wall lesson1_dangling.cpp -o lesson1_dangling

你可能会看到编译器给 warning。

即使程序跑出来一个值,也不要以为“能跑就是对”。

这里要记住一句工程判断:

只要生命周期错了,程序就已经错了;没崩只是运气好。

6. 今天第三个实验:用 gdb 看变量

运行:

gdb ./lesson1_memory

进入后执行:

break test_stack
run
next
print a
print &a
next
print b
print &b
continue

你今天只需要学会 4 个 gdb 动作:

  • break 函数名

  • run

  • next

  • print 变量

五、今天必须掌握的 6 个概念

1. p和 *p的区别

  • p:地址

  • *p:地址里存的值

2. &a是什么

  • 变量 a 的地址

3. 栈变量什么时候失效

  • 函数返回或作用域结束后

4. 堆内存谁负责释放

  • 申请它的人负责释放,C++ 现代写法会进一步交给 RAII/智能指针管理

5. 为什么返回局部变量地址危险

  • 因为生命周期结束,地址不再可靠

6. 为什么必须会看地址

  • 后面调试 buffer、网络收发、SDK 指针传递时,不会再只靠猜

六、今天的练习题

练习 1:自己写一个交换函数

要求你分别写:

错误版本

void swap_bad(int a, int b)

正确版本

void swap_good(int* a, int* b)

观察为什么前者交换失败,后者成功。

练习 2:写一个函数返回堆内存

例如:

int* create_value(int x)

函数内部用 new 分配,返回地址,在 main 中打印并释放。

体会:

  • 这种写法生命周期是“可继续存在”的

  • 但要自己 delete

练习 3:写一个数组与指针实验

例如:

int arr[5] = {1,2,3,4,5};

打印:

  • arr

  • &arr[0]

  • arr[1]

  • *(arr + 1)

体会数组名和首元素地址的关系。

练习 4:gdb 调试一次

自己给程序打断点,打印:

  • 局部变量值

  • 局部变量地址

  • 指针值

  • *指针

七、今天的输出要求

1. 一个目录

cpp-foundation-labs/
└── lesson01_memory/
    ├── lesson1_memory.cpp
    ├── lesson1_dangling.cpp
    └── notes.md

2. 一个 notes.md

里面只写这几项:

# Lesson 01 - 指针与内存

## 我今天学了什么
- 指针保存的是地址
- *p 表示解引用
- 局部变量生命周期结束后地址会失效

## 我做了哪些实验
- 栈/堆/全局变量地址打印
- 返回局部变量地址的错误实验
- gdb 打断点看变量地址

## 我遇到的问题
- 例如:不理解 p 和 &p 的区别

## 我现在能回答的问题
- 为什么不能返回局部变量地址
- 为什么堆内存需要释放

3. 一个 Git 提交

git init
git add .
git commit -m "lesson01: pointer memory lifetime basics"

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐