文章目录
C语言的手动内存管理机制虽然灵活,但也极易引发各类内存错误。这些错误往往具有隐蔽性强、难以调试的特点,是程序崩溃、安全漏洞的常见根源。
一、间接引用坏指针(Invalid Pointer Dereference)
本质原因:指针未初始化或指向已释放内存区域。
示例代码:
int *p = NULL; // 未初始化指针
*p = 42; // 崩溃:访问0x0地址
int *q = malloc(sizeof(int));
free(q);
*q = 100; // 悬挂指针:写入已释放内存
系统级分析:
- 现代操作系统通过虚拟内存机制隔离进程地址空间
- 访问非法地址会触发段错误(Segmentation Fault)
/proc/[pid]/maps
文件可查看进程合法内存区域
二、内存泄漏(Memory Leak)
本质原因:动态分配的内存未释放,导致可用内存逐渐耗尽。
示例代码:
void leaky_func() {
int *arr = malloc(1024 * sizeof(int));
// 使用后忘记free(arr)
}
int main() {
while(1) {
leaky_func(); // 每次泄漏4KB
sleep(1);
}
}
检测工具:
valgrind --leak-check=full ./program
扩展案例:
- 文件描述符泄漏(未close)
- 互斥锁泄漏(未pthread_mutex_destroy)
三、缓冲区溢出(Buffer Overflow)
本质原因:写入超出分配内存边界的数据。
示例代码:
char buf[4];
strcpy(buf, "ABCDE"); // 写入5字节+终止符到4字节空间
void vulnerable() {
char buffer[8];
gets(buffer); // 无边界检查的输入
}
安全危害:
- 覆盖返回地址实现代码注入攻击
- 修改相邻变量导致逻辑错误
防护方案:
// 使用安全函数替代
strncpy(dest, src, dest_size-1);
dest[dest_size-1] = '\0';
fgets(buffer, sizeof(buffer), stdin);
四、未初始化内存读取(Uninitialized Read)
本质原因:使用未赋值的自动变量或堆内存。
示例代码:
int func() {
int x; // 自动变量未初始化
return x + 5; // 读取未知值
}
int *p = malloc(sizeof(int));
printf("%d", *p); // 读取未初始化的堆内存
系统级表现:
- 栈内存残留数据可能导致信息泄露
- 不同编译器对自动变量初始化策略不同(Debug/Release模式差异)
五、双重释放(Double Free)
本质原因:同一块堆内存被多次释放。
示例代码:
int *p = malloc(sizeof(int));
free(p);
free(p); // 危险操作!
// 更隐蔽的情况
int *q = p;
free(q);
free(p); // 实际释放同一内存块
后果分析:
- 破坏堆管理器的元数据结构
- 可能被利用实现任意代码执行(Use-after-free攻击)
六、内存对齐问题(Misaligned Access)
本质原因:访问未按硬件要求对齐的内存地址。
示例代码:
char data[10];
int *p = (int*)(data + 1); // 地址未4字节对齐
*p = 0x12345678; // 在RISC架构上崩溃
硬件差异:
架构 | 对齐要求 | 未对齐访问后果 |
---|---|---|
x86 | 非强制 | 性能下降 |
ARM | 强制 | 触发总线错误(SIGBUS) |
RISC | 强制 | 崩溃 |
七、错误的内存释放方式
错误类型:
- 释放栈内存
- 释放部分内存块
- 使用错误释放函数
// 1. 释放栈内存
int x;
free(&x); // 错误!
// 2. 释放部分内存块
int *arr = malloc(10*sizeof(int));
free(arr + 5); // 错误地址!
// 3. 使用错误释放函数
void *p = memalign(64, 1024);
free(p); // 应使用aligned_free(p)
内存分配器原理:
malloc/free
维护空闲块链表- 错误地址释放会破坏链表结构
munmap
系统调用实际释放物理内存