指针的本质:内存地址与变量的关系

指针这东西,说白了就是地址。你想想看,我们写程序时声明的每个变量,在内存里都有个“门牌号”。指针就是用来存这个门牌号的变量。

我记得刚入行那会儿,有个同事把指针和它指向的值搞混了,调试了一整天没找到bug。后来我帮他一看,问题就出在他没搞明白——指针存的是地址,不是值本身。

核心概念:变量在内存中有两个属性——地址和值。指针变量专门用来存储其他变量的地址。

举个例子:

int a = 42;      // a 是普通变量,存的是值 42
int *p = &a;    // p 是指针变量,存的是 a 的地址

这里 a 在内存中占4个字节(假设是32位系统),它的地址可能是 0x7ffd1234。而 p 这个指针变量里存的就是 0x7ffd1234 这个数字。嗯,就是这么简单。

指针的声明与初始化

声明指针时,* 号告诉编译器:“我要搞一个指针变量”。类型说明符告诉编译器:“这个指针将来要指向什么类型的变量”。

int *p;          // 指向整型的指针
char *cp;        // 指向字符的指针
double *dp;      // 指向双精度浮点数的指针
void *vp;        // 无类型指针(可以指向任何类型)

我曾经踩过的坑:声明指针后不初始化就直接使用。未初始化的指针里存的是垃圾值,解引用它等于访问随机内存地址——轻则段错误,重则静默破坏数据。

我个人习惯是:声明指针的同时就初始化,要么赋一个有效地址,要么赋 NULL。

int *p = NULL;    // 好习惯:初始化为空指针
int *q = &some_var; // 更好:直接指向有效变量

取地址符 & 与解引用符 *

这两个符号是操作指针的左右手:

  • & 取地址符:获取变量的内存地址
  • * 解引用符:通过指针访问它指向的变量

看个完整的例子:

int value = 100;
int *ptr = &value;   // & 取出 value 的地址,赋给 ptr

printf("value 的值: %d\n", value);    // 输出 100
printf("ptr 的值: %p\n", ptr);        // 输出地址,如 0x7ffd1234
printf("解引用 ptr: %d\n", *ptr);     // * 取出 ptr 指向的值,输出 100

*ptr = 200;          // 通过指针修改 value
printf("修改后 value: %d\n", value);  // 输出 200

我在项目中遇到过有人把 & 和 * 搞反了。比如想取地址却用了 *,想解引用却用了 &。编译器会报类型不匹配的警告,但很多人直接忽略警告——结果运行时崩得一塌糊涂。

记忆技巧:声明时 * 表示“这是一个指针”,使用时 * 表示“取指针指向的值”。& 在任何地方都是“取地址”。

指针与变量的关系图

下面这张图能帮你直观理解指针、变量和内存地址之间的关系:

指针与变量的内存关系 变量 value 地址: 0x7ffd1234 值: 100 指针 ptr 地址: 0x7ffd1240 值: 0x7ffd1234 ptr 存的是 value 的地址 *ptr 解引用 → 找到地址 0x7ffd1234 → 取出值 100 *ptr = 200 → 找到地址 0x7ffd1234 → 把值改为 200 指针本身也有地址(0x7ffd1240),但它存的是别人的地址 这就是“间接访问”的本质

常见误区与避坑指南

误区 错误示例 正确做法
混淆指针与普通变量 int *p; *p = 10;(p未初始化) 先让p指向有效内存,再解引用
忘记初始化指针 int *p; scanf("%d", p); int *p = &var; scanf("%d", p);
解引用空指针 int *p = NULL; *p = 5; 使用前检查 if (p != NULL)
类型不匹配 double d; int *p = &d; 指针类型必须与指向的变量类型一致

我曾经犯过的错:写了一个链表操作函数,忘记初始化头指针就直接插入节点。结果程序跑起来偶尔正常、偶尔崩溃。查了两天才发现是未初始化的指针指向了某个恰好可写的内存区域——这种bug最要命,因为它不一定会立即崩溃。

指针的 sizeof

指针变量本身占多大内存?这取决于系统架构:

  • 32位系统:指针占 4字节
  • 64位系统:指针占 8字节

不管指针指向的是 char、int 还是 double,指针本身的大小都一样。为什么?因为地址的长度是固定的,跟地址里存的是什么类型的数据无关。

printf("char* 大小: %zu\n", sizeof(char*));   // 64位系统输出 8
printf("int* 大小: %zu\n", sizeof(int*));     // 64位系统输出 8
printf("double* 大小: %zu\n", sizeof(double*)); // 64位系统输出 8

嗯,这里要注意:sizeof(指针) 和 sizeof(指针指向的类型) 是两个完全不同的概念。前者是地址的大小,后者是目标数据的大小。

个人经验:写跨平台代码时,不要假设指针的大小。用 sizeof 运算符动态获取,或者使用 intptr_t / uintptr_t 这种与指针等宽的整数类型。

小结

指针就是地址的容器。声明指针用 *,取地址用 &,解引用用 *。记住这三句话,指针的基本功就扎实了:

  1. 指针存的是地址,不是值
  2. 声明时初始化,避免野指针
  3. 解引用前确保指针有效

下一节我们会深入指针的运算和数组的关系。不过在那之前,先把这些基础练熟——我见过太多人基础没打牢就急着搞高级用法,结果越学越乱。

可可精品资料库