指针与内存:指针的本质、野指针与悬空指针、指针运算与内存越界
指针这东西,说难也难,说简单也简单。我做了十几年嵌入式开发,见过太多人在指针上栽跟头。说白了,指针就是一个存放地址的变量。你想想看,它本身不存数据,它存的是数据在内存里的门牌号。
指针的本质
每个变量在内存里都有个地址。指针就是用来装这个地址的。我习惯把指针比作快递单——上面写的不是物品本身,而是物品存放的位置。
int a = 10;
int *p = &a; // p 存的是 a 的地址
printf("%d", *p); // 通过地址找到 a,输出 10
这里有个关键点:int *p 声明了一个指针变量,&a 取 a 的地址,*p 是解引用操作。嗯,就这么简单。
核心理解:指针的大小在 32 位系统上是 4 字节,64 位系统上是 8 字节。不管它指向什么类型,指针本身的大小是固定的。
野指针与悬空指针
这两个概念我经常被问到。它们有什么区别?我举个例子你就明白了。
野指针
野指针就是没有初始化的指针。它里面存的是个随机地址。你想想看,拿着一个随机地址去访问内存,不出问题才怪。
int *p; // 野指针!p 的值是随机的
*p = 10; // 危险!不知道写到哪里去了
我在项目中遇到过这样的代码。一个同事定义了一个指针变量,忘了初始化,结果程序跑起来后莫名其妙地崩溃。查了两天才找到原因。
避坑指南:定义指针时一定要初始化。如果暂时不知道指向哪里,就赋值为 NULL。
悬空指针
悬空指针是指向的内存已经被释放了,但指针还在。说白了就是拿着过期的门牌号去找人。
int *p = (int *)malloc(sizeof(int));
free(p); // 释放内存
*p = 10; // 悬空指针!内存已经还给系统了
我曾经在一个通信协议栈里遇到过这种问题。释放了缓冲区后,代码里还有一处引用了这个指针。结果数据被写到了已经释放的内存里,导致另一个线程分配到了这块内存,数据全乱了。
我的习惯:每次 free 之后,立刻把指针置为 NULL。这样再访问时就会立即崩溃,而不是在诡异的地方出错。
指针运算与内存越界
指针运算说白了就是地址的加减。但这里有个坑——指针加减不是简单的地址加减,而是根据指向的类型来计算的。
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // p 指向 arr[0]
p++; // p 指向 arr[1],地址增加了 4 字节(int 的大小)
printf("%d", *p); // 输出 2
为什么会这样?因为编译器知道 p 是 int 类型的指针,所以 p++ 实际上是 p + sizeof(int)。这个设计很巧妙,但也很容易让人犯错。
内存越界
内存越界就是访问了不属于你的内存区域。我见过最典型的场景是数组访问越界。
int arr[3] = {1, 2, 3};
arr[3] = 10; // 越界!arr 只有 3 个元素,索引 0-2
你想想看,arr[3] 访问的是数组后面的内存。那块内存可能属于另一个变量,也可能是堆上的数据。写进去之后,程序可能不会立即崩溃,但会在某个时刻出现奇怪的行为。
我曾经踩过的坑:在一个嵌入式项目中,我写了个循环处理缓冲区数据。循环条件写成了 i <= size,多了一次越界访问。结果把紧挨着的任务控制块给覆盖了,系统调度直接乱掉。查了整整一个下午。
知识体系结构图
总结一下
指针这东西,用好了是利器,用不好就是凶器。我个人总结了三条铁律:
- 定义指针必须初始化——不知道指向哪里就赋 NULL
- 释放内存后立即置 NULL——防止悬空指针
- 访问前检查边界——数组下标、指针偏移都要确认范围
做到这三点,指针相关的内存问题能减少八成。剩下的两成,靠调试经验和工具来补。嗯,今天就聊到这里。
一个小技巧:在嵌入式开发中,我习惯在调试阶段开启内存保护单元(MPU),这样一旦发生越界访问,系统会立即触发异常,定位问题非常快。
可可精品资料库