指针的本质:内存地址与间接访问
指针这东西,说白了就是地址。你想想看,我们平时用变量,比如 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,而是指向一块随机的、可能已经被释放或占用的内存。
为什么会这样?主要有三种情况:
- 未初始化——声明了指针,没给地址
- 指向已释放的内存——free 或 delete 之后没置空
- 指向栈上局部变量——函数返回后,局部变量没了,指针还留着
我在项目中遇到过最典型的一个 bug:
int* getPtr() {
int x = 100;
return &x; // 危险!x是局部变量
}
int* p = getPtr();
*p = 200; // 崩溃?不一定,但行为未定义
这个函数返回后,x 的内存已经被回收了。但 p 还记着那个地址。你用它写数据,可能写到栈上别的变量里,也可能直接触发段错误。最坑的是——它有时候能跑,有时候崩,完全看运气。
警告:野指针的危害不在于「一定会崩」,而在于「不一定崩」。你测试时一切正常,上线后随机崩溃,这种 bug 最难查。我曾经花了两天时间,才定位到一个野指针导致的偶发崩溃。
指针与内存布局
为了让你更直观地理解指针在内存中的样子,我画了一张图:
你看,p 本身也有自己的地址(0x7fff0020),但它里面存的是 a 的地址。通过 *p 就能间接操作 a。这就是间接访问的精髓。
避坑指南
我曾经踩过的坑,希望你避开:
- 指针声明后立即初始化,要么给有效地址,要么给 nullptr
- free/delete 后立即将指针置为 nullptr
- 不要返回局部变量的地址
- 多线程中共享指针要加锁,或者用原子操作
- 使用智能指针(shared_ptr/unique_ptr)代替裸指针
嗯,这里要注意一点:很多人觉得智能指针万能,其实不是。智能指针解决的是生命周期问题,但解决不了数据竞争。多线程里该加锁还是得加锁。
最后说一句:指针是 C/C++ 最强大的特性,也是最危险的。你尊重它,它帮你写出高效的代码;你不尊重它,它让你加班到凌晨三点。
我的习惯:每次写指针操作前,先问自己三个问题——这个指针初始化了吗?它指向的内存还活着吗?有没有其他线程同时在操作它?问完再动手。
可可精品资料库