指针与内存:指针的本质、野指针与悬空指针、指针运算与内存越界

指针这东西,说难也难,说简单也简单。我做了十几年嵌入式开发,见过太多人在指针上栽跟头。说白了,指针就是一个存放地址的变量。你想想看,它本身不存数据,它存的是数据在内存里的门牌号。

指针的本质

每个变量在内存里都有个地址。指针就是用来装这个地址的。我习惯把指针比作快递单——上面写的不是物品本身,而是物品存放的位置。

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,多了一次越界访问。结果把紧挨着的任务控制块给覆盖了,系统调度直接乱掉。查了整整一个下午。

知识体系结构图

指针与内存 指针的本质 存放地址的变量 32位4字节,64位8字节 解引用操作 *p 野指针与悬空指针 野指针:未初始化 悬空指针:释放后未置空 free后立即置NULL 指针运算与越界 加减按类型大小计算 数组越界访问 越界导致数据覆盖 核心原则:初始化、检查边界、释放后置空 避免野指针、悬空指针和内存越界

总结一下

指针这东西,用好了是利器,用不好就是凶器。我个人总结了三条铁律:

  • 定义指针必须初始化——不知道指向哪里就赋 NULL
  • 释放内存后立即置 NULL——防止悬空指针
  • 访问前检查边界——数组下标、指针偏移都要确认范围

做到这三点,指针相关的内存问题能减少八成。剩下的两成,靠调试经验和工具来补。嗯,今天就聊到这里。

一个小技巧:在嵌入式开发中,我习惯在调试阶段开启内存保护单元(MPU),这样一旦发生越界访问,系统会立即触发异常,定位问题非常快。

可可精品资料库