指针的本质:内存地址与变量的关系
指针这东西,说白了就是地址。你想想看,我们写程序时声明的每个变量,在内存里都有个“门牌号”。指针就是用来存这个门牌号的变量。
我记得刚入行那会儿,有个同事把指针和它指向的值搞混了,调试了一整天没找到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
我在项目中遇到过有人把 & 和 * 搞反了。比如想取地址却用了 *,想解引用却用了 &。编译器会报类型不匹配的警告,但很多人直接忽略警告——结果运行时崩得一塌糊涂。
记忆技巧:声明时 * 表示“这是一个指针”,使用时 * 表示“取指针指向的值”。& 在任何地方都是“取地址”。
指针与变量的关系图
下面这张图能帮你直观理解指针、变量和内存地址之间的关系:
常见误区与避坑指南
| 误区 | 错误示例 | 正确做法 |
|---|---|---|
| 混淆指针与普通变量 | 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 这种与指针等宽的整数类型。
小结
指针就是地址的容器。声明指针用 *,取地址用 &,解引用用 *。记住这三句话,指针的基本功就扎实了:
- 指针存的是地址,不是值
- 声明时初始化,避免野指针
- 解引用前确保指针有效
下一节我们会深入指针的运算和数组的关系。不过在那之前,先把这些基础练熟——我见过太多人基础没打牢就急着搞高级用法,结果越学越乱。