指针的本质:内存地址与间接访问

指针这东西,说白了就是地址。你想想看,我们平时用变量,比如 int a = 10,编译器会在内存里给你划一块地方,把 10 存进去。那这块地方的门牌号是多少?指针就是用来记这个门牌号的。

我个人习惯把指针理解成「快递单」。变量是包裹,内存地址是收货地址,指针就是那张写着地址的单子。你拿着单子,就能找到包裹,打开它,取出里面的东西。这就是间接访问——不直接操作包裹,而是通过地址去找。

指针的声明与初始化

声明指针其实很简单:

int* p;   // 声明一个指向整型的指针
char* cp; // 指向字符的指针
void* vp; // 万能指针,啥都能指

但注意,声明和初始化是两码事。光声明不初始化,这个指针就是野的。我见过太多新手犯这个错:

int* p;       // 声明了,没初始化
*p = 42;      // 危险!p指向哪里?不知道!

正确的做法是:

int a = 10;
int* p = &a;  // p保存了a的地址
*p = 20;      // 现在a变成了20

这里 & 是取地址符,* 是解引用符。一个取地址,一个取内容,正好相反。

小技巧:我习惯在声明指针时就初始化为 nullptr,哪怕后面马上赋值。这样至少不会出现未初始化的野指针。

野指针的成因与危害

野指针,就是指向「不该去的地方」的指针。它不像空指针那样明确指向 0,而是指向一块随机的、可能已经被释放或占用的内存。

为什么会这样?主要有三种情况:

  1. 未初始化——声明了指针,没给地址
  2. 指向已释放的内存——free 或 delete 之后没置空
  3. 指向栈上局部变量——函数返回后,局部变量没了,指针还留着

我在项目中遇到过最典型的一个 bug:

int* getPtr() {
    int x = 100;
    return &x;  // 危险!x是局部变量
}

int* p = getPtr();
*p = 200;  // 崩溃?不一定,但行为未定义

这个函数返回后,x 的内存已经被回收了。但 p 还记着那个地址。你用它写数据,可能写到栈上别的变量里,也可能直接触发段错误。最坑的是——它有时候能跑,有时候崩,完全看运气。

警告:野指针的危害不在于「一定会崩」,而在于「不一定崩」。你测试时一切正常,上线后随机崩溃,这种 bug 最难查。我曾经花了两天时间,才定位到一个野指针导致的偶发崩溃。

指针与内存布局

为了让你更直观地理解指针在内存中的样子,我画了一张图:

指针与内存地址关系图 变量 a 值: 10 地址: 0x7fff0010 指针 p 值: 0x7fff0010 地址: 0x7fff0020 *p 解引用操作 通过 p 中保存的地址 (0x7fff0010) 找到变量 a,读取/修改其值

你看,p 本身也有自己的地址(0x7fff0020),但它里面存的是 a 的地址。通过 *p 就能间接操作 a。这就是间接访问的精髓。

避坑指南

我曾经踩过的坑,希望你避开:

  • 指针声明后立即初始化,要么给有效地址,要么给 nullptr
  • free/delete 后立即将指针置为 nullptr
  • 不要返回局部变量的地址
  • 多线程中共享指针要加锁,或者用原子操作
  • 使用智能指针(shared_ptr/unique_ptr)代替裸指针

嗯,这里要注意一点:很多人觉得智能指针万能,其实不是。智能指针解决的是生命周期问题,但解决不了数据竞争。多线程里该加锁还是得加锁。

最后说一句:指针是 C/C++ 最强大的特性,也是最危险的。你尊重它,它帮你写出高效的代码;你不尊重它,它让你加班到凌晨三点。

我的习惯:每次写指针操作前,先问自己三个问题——这个指针初始化了吗?它指向的内存还活着吗?有没有其他线程同时在操作它?问完再动手。

可可精品资料库