一、指针是什么:从内存地址说起

大家好,我是你们这门课的老朋友。今天咱们聊一个C语言里绕不开的话题——指针。

说实话,我当年学指针的时候,也被绕得晕头转向。后来在嵌入式项目里栽了几个跟头,才真正搞明白它到底是个什么东西。今天我就把那些经验掰开了揉碎了讲给你听。

1.1 内存地址的概念

先问一个问题:你电脑里的内存,到底长什么样?

说白了,内存就是一大排连续的“小格子”。每个格子有8个比特位,也就是1个字节。每个格子都有一个唯一的编号,这个编号就是内存地址

举个例子:

内存地址:  0x1000   0x1001   0x1002   0x1003   ...
内容:      [0x12]   [0x34]   [0x56]   [0x78]   ...

地址从0开始,一直往上排。32位系统能访问0x00000000到0xFFFFFFFF,总共4GB空间。64位系统就更大了,理论上能到16EB。

核心理解:内存地址就是内存单元的“门牌号”。你知道了门牌号,就能找到对应的数据。

我在做嵌入式开发时,经常要直接操作寄存器。那些寄存器就映射在固定的内存地址上。比如某个GPIO控制寄存器的地址是0x40021000,你往这个地址写数据,就能控制LED亮灭。这就是地址的威力。

1.2 指针变量的定义与初始化

指针是什么?指针就是一个存放内存地址的变量

普通变量存的是数据,比如int a = 10; 变量a里存的是10。指针变量存的是地址,比如int *p = &a; 变量p里存的是a的地址。

定义指针的语法很简单:

类型名 *指针变量名;

比如:

int *p;      // 指向int类型数据的指针
char *pc;    // 指向char类型数据的指针
float *pf;   // 指向float类型数据的指针

初始化指针,一般用取地址符&:

int a = 100;
int *p = &a;   // p保存了a的地址

也可以初始化为空指针:

int *p = NULL;  // 指向空,表示不指向任何有效地址

我的习惯:定义指针时一定初始化。要么给有效地址,要么给NULL。野指针是C语言里最坑的东西之一,我早期项目里因为这个吃过不少苦头。

1.3 取地址符&与解引用符*

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

&:取地址符

放在变量前面,得到该变量的内存地址。

int x = 42;
printf("x的地址是:%p\n", &x);  // 输出类似 0x7ffd5a3b4c08

*:解引用符

放在指针变量前面,得到该指针指向的内存单元里的数据。

int x = 42;
int *p = &x;
printf("p指向的值是:%d\n", *p);  // 输出42

说白了,&是“找到你的门牌号”,*是“按门牌号进去拿东西”。

我曾经踩过的坑:解引用一个未初始化的指针,程序直接崩溃。比如:

int *p;   // 没初始化,p里是随机地址
*p = 10;  // 往随机地址写数据,大概率段错误

所以记住:指针不初始化,千万别解引用

1.4 指针的大小与类型

指针本身也是变量,它也要占内存。那么一个指针变量占几个字节?

答案是:取决于系统位数

系统位数 指针大小 可寻址范围
16位系统 2字节 64KB
32位系统 4字节 4GB
64位系统 8字节 16EB

你可以用sizeof验证一下:

printf("int*的大小:%zu字节\n", sizeof(int*));
printf("char*的大小:%zu字节\n", sizeof(char*));
printf("void*的大小:%zu字节\n", sizeof(void*));

你会发现,不管指向什么类型,同一系统下所有指针的大小都一样。为什么?因为指针存的是地址,地址的长度是固定的。

那为什么还要区分类型呢?

嗯,这里有个关键点:指针的类型决定了它如何解释内存中的数据

举个例子:

int a = 0x12345678;
int *p_int = &a;
char *p_char = (char*)&a;

printf("*p_int = 0x%x\n", *p_int);   // 输出 0x12345678
printf("*p_char = 0x%x\n", *p_char); // 输出 0x78(小端模式)

同样的地址,int*指针一次读4个字节,char*指针一次读1个字节。类型告诉编译器:从这个地址开始,取多少字节,怎么解释

一句话总结:指针的大小由系统位数决定,指针的类型由它指向的数据类型决定。大小管“能访问多大范围”,类型管“怎么访问数据”。

知识体系总览

下面这张图帮你理清本章的核心逻辑:

指针知识体系 指针 内存地址 指针变量 & 和 * 操作符 每个字节有唯一编号 地址范围决定寻址能力 定义:类型 *变量名 初始化:&变量 或 NULL 大小由系统位数决定 &:取地址 *:解引用(取值) 类型决定解释方式

小结

这一章我们讲了四个核心点:

  • 内存地址:内存单元的编号,是访问数据的“门牌号”
  • 指针变量:存放地址的变量,定义时要初始化
  • &和*:取地址和解引用,一个找门牌,一个进门拿东西
  • 大小与类型:大小由系统决定,类型决定如何解释数据

指针这东西,刚开始觉得绕,但一旦理解了“地址”这个概念,后面就顺了。我当年在调试一个串口驱动时,就是因为没搞清指针类型,读出来的数据全是乱的。从那以后,我对指针的类型格外敏感。

下一章咱们聊聊指针的运算,那才是真正开始玩指针的地方。


公众号:蓝海资料掘金营,微信deep3321