指针与内存管理
指针是 C 的灵魂,也是嵌入式工程师必须掌握的核心。
一、指针基础
int x = 42;
int *p = &x; // p 指向 x
*p = 100; // 改 x 的值
int **pp = &p; // 指向指针的指针
int arr[5] = {1,2,3,4,5};
int *q = arr; // 数组名隐式转指针
*(q + 2) == arr[2] // 3
q[2] == arr[2] // 数组下标是 *(q+2) 的语法糖
void *vp = malloc(16); // void* 可转任何指针(C 不必强转;C++ 要)
free(vp);
二、指针算术
char *cp = (char*)arr; // 每次 +1 移动 1 字节
int *ip = arr; // 每次 +1 移动 sizeof(int)=4 字节
三、内存布局(典型进程)
低地址
┌────────────────┐ 0x0000_0000
│ NULL │
├────────────────┤
│ .text 代码段 │ 只读、可执行
├────────────────┤
│ .rodata 常量 │ 只读
├────────────────┤
│ .data 已初始化 │ 可读写
├────────────────┤
│ .bss 未初始化 │ 可读写,运行时清零
├────────────────┤
│ heap 堆 │ malloc / new 向上增长
│ ... │
│ ↓ │
│ ↑ │
│ stack 栈 │ 局部变量、函数调用
├────────────────┤ 0xFFFF_FFFF
高地址
注:实际地址受 ASLR、MMU 影响,但相对位置不变。
四、栈帧(Stack Frame)
void foo(int a) { // a 在栈
int local; // local 在栈
char buf[64]; // buf 在栈(注意溢出!)
bar(local); // 压栈:返回地址、参数
}
栈帧布局(高位在上):
┌────────────────┐ ← 旧栈帧
│ 旧栈帧内容 │
├────────────────┤
│ 返回地址 (LR) │
├────────────────┤
│ 上一帧 FP │
├────────────────┤ ← FP (frame pointer)
│ 局部变量 │
└────────────────┘ ← SP
五、堆与栈对比
| 特性 | 栈 | 堆 |
|---|---|---|
| 分配速度 | 快(指针下移) | 慢(可能寻空闲块) |
| 空间 | 较小(MB 级) | 较大(GB 级) |
| 生命周期 | 自动 | 手动 |
| 碎片 | 无 | 可能有 |
| 多线程 | 每线程独立 | 共享 |
| 嵌入式 | 实时性更好 | 慎用(碎片、OOM) |
六、内存对齐(Alignment)
struct s {
char a; // 1 字节
int b; // 4 字节,对齐到 4 边界
}; // 总大小 8(不是 5!)
为什么要对齐?
- CPU 访问未对齐数据可能:性能下降、抛异常(ARMv7 默认支持,但慢)
- 硬件寄存器、DMA buffer 必须对齐到自然边界
对齐控制:
_Alignas(16) int buf[4]; // C11
int buf[4] __attribute__((aligned(16))); // GCC
#pragma pack(1) // 紧凑(1字节对齐)
七、常见内存错误
| 错误 | 现象 | 工具 |
|---|---|---|
| 缓冲区溢出 | 数据破坏、栈溢出、漏洞 | ASan / Stack Smasher |
| Use After Free | 数据破坏、崩溃 | ASan |
| Double Free | 堆破坏 | ASan |
| 内存泄漏 | 长时间运行 OOM | Valgrind / ASan / LSan |
| 未初始化内存 | 随机值 | MSan |
// Valgrind 推荐用法
valgrind --tool=memcheck --leak-check=full ./program
// GCC AddressSanitizer
gcc -fsanitize=address -g program.c -o program
八、嵌入式专用技巧
- 内存池:避免 malloc 碎片,预先分配固定大小块
- DMA 缓冲区:cache 对齐(如
dma_alloc_coherent返回的地址) - 双缓冲:生产者 / 消费者避免冲突
- 环形缓冲区:无锁队列基础
- 位域:
struct flags {
uint32_t enable : 1;
uint32_t mode : 3;
uint32_t : 4; // 填充
uint32_t value : 24;
};