2、指针与基本类型:int指针、char指针、float指针、void指针、指针的大小

好,咱们今天来聊聊指针和基本类型的关系。说实话,很多初学者觉得指针难,其实一大半的困惑都卡在这里——「为什么不同类型的指针,长得好像差不多,用起来却天差地别?」

我个人习惯把指针理解成「带类型标签的地址」。地址本身只是个数字,但有了类型,编译器才知道怎么去解读那块内存。你想想看,这就像快递柜的格子——每个格子都有编号(地址),但格子大小不同,里面装的东西也不同。

2.1 int指针、char指针、float指针——它们到底差在哪?

先看一段最基础的代码:

int a = 42;
int *p_int = &a;

char c = 'A';
char *p_char = &c;

float f = 3.14f;
float *p_float = &f;

这三个指针,p_intp_charp_float,它们存储的都是地址。但区别在哪?

核心区别一:解引用时读取的字节数不同

  • *p_int 会从地址开始读取 4 个字节(假设 int 是 32 位)
  • *p_char 只读取 1 个字节
  • *p_float 读取 4 个字节,但按浮点数格式解释

我在项目中遇到过这样一个坑:有人用 char* 去遍历一个 int 数组,想逐字节查看数据。想法没错,但写代码时忘了做强制类型转换,结果解引用出来全是乱码。嗯,这里要注意——指针的类型决定了编译器怎么「切」这块内存

关键理解:指针的类型不是给地址本身贴标签,而是告诉编译器「从这个地址开始,按什么规则读取/写入数据」。

核心区别二:指针运算的步长不同

这个我当年学的时候也迷糊过一阵子。看例子:

int arr_int[3] = {10, 20, 30};
char arr_char[3] = {'A', 'B', 'C'};

int *pi = arr_int;
char *pc = arr_char;

pi++;  // 地址增加 sizeof(int) = 4 字节
pc++;  // 地址增加 sizeof(char) = 1 字节

为什么会这样?因为 p + 1 在 C 语言里不是简单地加 1,而是加 sizeof(指针指向的类型)。说白了,编译器帮你做了乘法——p + n 实际地址 = p + n * sizeof(*p)

指针类型 sizeof(指向类型) p + 1 增加的字节数 典型用途
int* 4 4 遍历 int 数组
char* 1 1 遍历字符串、逐字节操作
float* 4 4 遍历 float 数组
double* 8 8 遍历 double 数组

小技巧:如果你不确定指针加 1 到底跳多少字节,直接打印 sizeof(*p) 看看。我调试时经常这么干,比翻手册快多了。

2.2 void指针——「万能插座」也有规矩

void* 是个特殊的存在。它只存地址,不存类型信息。你可以把任何类型的指针赋值给 void*

int a = 100;
void *vp = &a;  // 合法,不报错

char c = 'X';
vp = &c;        // 也合法

但注意——你不能直接解引用 void 指针。因为编译器不知道它指向什么类型,不知道该读几个字节。

// 错误写法
// printf("%d", *vp);  // 编译报错

// 正确写法:先转回具体类型
printf("%d", *(int*)vp);

我曾经在写一个通用数据缓存模块时,大量用了 void*。好处是接口很通用,任何类型的数据都能往里塞。坏处是——你必须在取出时记得原来的类型,否则解引用就崩了。嗯,这就像你把钥匙放进了万能抽屉,但得记住哪把钥匙开哪把锁。

避坑指南:我曾经在嵌入式项目里犯过一个错——用 void* 传了一个结构体指针,然后在回调函数里忘了做类型转换就直接解引用。结果程序跑起来随机崩溃,查了两天才找到原因。记住:void* 只负责「搬运」地址,不负责「解释」数据。

2.3 指针的大小——跟类型无关,跟平台有关

这个问题我面试时经常问,很多人会答错。你猜猜看,在同一个平台上,int*char*float*void* 的大小分别是多少?

答案是——它们的大小完全一样

为什么?因为指针存的是地址,而地址的长度只取决于 CPU 的寻址能力,跟指向什么类型的数据无关。

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

在 32 位系统上,输出全是 4 字节。在 64 位系统上,输出全是 8 字节。

平台 地址总线宽度 指针大小
8 位单片机(如 8051) 16 位(部分) 2 字节(或混合模式)
32 位 ARM/x86 32 位 4 字节
64 位 x86_64/ARM64 64 位 8 字节

重要结论:指针的大小由「地址总线宽度」决定,与指针本身的类型无关。但指针的「步长」和「解引用方式」由指向的类型决定。这两个概念千万别搞混了。

我在做嵌入式开发时,经常需要在 32 位 MCU 和 64 位 Linux 之间移植代码。如果代码里写了 sizeof(int*) 的硬编码假设(比如假设永远是 4),那移植到 64 位平台就炸了。所以我的习惯是——永远用 sizeof(指针) 动态获取大小,别写死数字

2.4 本章知识结构图

下面这张图帮你理清指针与基本类型的关系:

指针与基本类型 · 知识结构 指针 类型决定解引用方式 int* → 读4字节,按int解释 char* → 读1字节,按char解释 float* → 读4字节,按float解释 类型决定指针运算步长 p + 1 = 地址 + sizeof(*p) int* → 步长4字节 char* → 步长1字节 大小由平台决定 32位系统 → 4字节 64位系统 → 8字节 所有指针类型大小相同 void*:只存地址,不存类型,需强制转换后解引用 核心:指针 = 地址 + 类型信息 类型决定「怎么读」,平台决定「地址占多大」

2.5 总结一下

这一章的内容其实就三句话:

  1. 指针的类型决定了你解引用时怎么解释那块内存,以及指针加减时的步长。
  2. void 指针是个「类型擦除」的工具,灵活但危险,用之前必须转回具体类型。
  3. 指针的大小跟类型无关,只跟 CPU 的寻址能力有关。32 位系统 4 字节,64 位系统 8 字节。

我个人觉得,理解这三条,指针的基本功就算打牢了。后面咱们聊数组和指针的关系时,你会发现这些知识会反复用到。


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