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

指针这东西,说白了就是地址的搬运工。我刚开始学C的时候,总觉得指针神神秘秘的,好像是什么高深莫测的东西。后来踩了几个坑才明白——指针没那么玄乎,它就是用来存地址的变量。

1.1 内存地址是什么?

计算机的内存,你可以想象成一排排带编号的柜子。每个柜子能放一个字节的数据,柜子上的编号就是地址。比如0x7ffeefbff5a8,这就是一个地址。

我习惯把内存比作酒店的房间号。房间号本身不是房间,但它能告诉你房间在哪。指针也是这个道理——它存的是地址,不是数据本身。

核心概念:内存地址是内存单元的编号,指针变量是存放这个编号的变量。

1.2 指针变量的声明

声明指针的语法很简单:

int *p;      // p是一个指向整型的指针
char *cp;    // cp是一个指向字符型的指针
double *dp;  // dp是一个指向双精度浮点型的指针

这里的星号(*)告诉编译器:我要声明一个指针变量。至于它指向什么类型,由前面的类型名决定。

嗯,这里要注意:int* p 和 int *p 写法都行,我个人习惯把星号靠近变量名——int *p。为什么?因为声明多个变量时不容易搞混:

int* p1, p2;   // p1是指针,p2是普通int变量
int *p1, *p2;  // p1和p2都是指针

你看,第一种写法容易让人误以为p2也是指针。我在项目中就见过同事因为这个写法栽过跟头。

1.3 指针的初始化

声明指针后,它里面存的是什么?垃圾值。就像你开了个酒店房间,没登记客人信息,谁也不知道里面住着谁。

所以,指针必须初始化:

int a = 10;
int *p = &a;   // p指向a的地址

char c = 'A';
char *cp = &c; // cp指向c的地址

取地址符(&)的作用,就是获取变量的内存地址。这就像查酒店房间号——你告诉前台客人名字,前台告诉你房间号。

警告:未初始化的指针就是野指针。我曾经在调试一个网络程序时,发现程序随机崩溃,查了两天才找到原因——一个指针没初始化就拿来用了。从那以后,我声明指针必初始化,要么给有效地址,要么给NULL。

1.4 指针与变量的关系图

为了让你更直观地理解,我画了一张图:

变量 a 值: 10 地址: 0x1000 指针 p 存: 0x1000 指针p通过地址0x1000找到变量a *p 等价于 a,值为 10 *p = 10 // 解引用得到值

这张图展示了指针的核心逻辑:指针p里存的是变量a的地址(0x1000),通过解引用操作*p就能拿到a的值(10)。

1.5 指针的大小

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

系统架构 指针大小 地址范围
32位 4字节 0 ~ 2^32-1
64位 8字节 0 ~ 2^64-1

不管指针指向什么类型——int、char、double还是结构体——在同一个系统上,所有指针的大小都一样。为什么?因为地址的长度是固定的。

小技巧:用 printf("%zu\n", sizeof(p)); 可以查看指针的大小。我在做跨平台开发时,经常用这个来确认当前系统的指针位数。

1.6 常见的指针初始化方式

总结一下,指针初始化有这几种方式:

  1. 取已有变量的地址:int *p = &a;
  2. 指向动态分配的内存:int *p = (int*)malloc(sizeof(int));
  3. 初始化为NULL:int *p = NULL;
  4. 指向数组首元素:int *p = arr; (数组名就是首地址)

我个人最推荐第三种——先给NULL。为什么?因为如果你不小心用了未初始化的指针,程序会立刻崩溃,你马上就能发现问题。而如果指针指向一个随机地址,程序可能跑几个小时才崩溃,查起来真要命。

避坑指南:我曾经在嵌入式项目里,一个指针没初始化,结果它恰好指向了某个寄存器地址。程序写数据时把寄存器值改了,导致整个系统死机。查了三天才定位到问题——就是一个没初始化的指针。从那以后,我写代码第一件事就是给指针赋NULL。

1.7 指针的指针?别怕

有时候你会看到这样的声明:int **pp; 这表示指向指针的指针。说白了,就是指针变量也有自己的地址,你可以再用一个指针指向它。

int a = 10;
int *p = &a;   // p指向a
int **pp = &p; // pp指向p

printf("%d\n", **pp); // 输出10

你想想看,这就像你找酒店房间:pp是前台登记本上的记录,告诉你p在哪个房间;p告诉你a在哪个房间;最后你找到a。多了一层间接寻址而已。

嗯,实际开发中二级指针常用在函数参数里,比如你想在函数内部修改指针的值:

void alloc_memory(int **ptr) {
    *ptr = (int*)malloc(sizeof(int));
    **ptr = 42;
}

int main() {
    int *p = NULL;
    alloc_memory(&p);
    printf("%d\n", *p); // 输出42
    free(p);
    return 0;
}

这个例子中,如果不传二级指针,函数内部修改的只是指针的副本,外部的p还是NULL。说白了,你想改变指针本身,就得传指针的地址。

1.8 小结

指针的本质就三句话:

  • 指针是变量,存的是地址
  • 声明指针要指定指向的类型
  • 用之前必须初始化,否则就是野指针

记住这三点,指针的基本功就算打牢了。后面我们聊指针运算和数组的关系,你会发现——其实没那么难。

可可精品资料库