指针与基本数据类型:从整型到void*,再到野指针的避坑指南
指针这东西,说白了就是地址。但不同数据类型的指针,背后藏着不少门道。我刚开始学C语言时,总觉得“指针不就是存个地址嘛,管它什么类型”。直到我在一个嵌入式项目里,因为char*和int*混用,导致数据解析全乱套,才真正明白——类型,就是指针的灵魂。
1. 指向整型的指针
整型指针,最常见,也最容易出问题。它的声明方式很简单:
int a = 42;
int *p = &a;
这里p存的是a的地址。通过*p可以读写a的值。但要注意,p+1并不是地址加1,而是加4(在32位系统上)。
核心理解:指针的类型决定了它“一步能跨多远”。int*走一步是4字节,char*走一步是1字节。这个差异,在数组遍历时尤其明显。
我个人习惯在声明指针时,把星号紧贴变量名:int *p。这样读起来更自然——“p是一个指针,指向int”。当然也有人写int* p,语法上没问题,但容易让人误以为int*是一种类型。其实C语言里,int* a, b这种写法,b只是int,不是指针。嗯,这里要注意。
2. 指向字符型的指针
字符指针,也就是char*,在C语言里几乎和字符串绑定在一起。你想想看,我们平时用的字符串常量,本质上就是char*。
char str[] = "hello";
char *p = str; // p指向字符串首字符
我在项目中遇到过一个问题:用char*指向字符串常量,然后试图修改它。结果程序直接崩溃。为什么?因为字符串常量存储在只读区,不能改。所以如果你需要修改字符串,一定要用数组,或者用malloc分配内存。
警告:不要用char*直接修改字符串常量。这是未定义行为,编译器不会报错,但运行时可能崩溃。
另外,char*做指针运算时,每次移动1字节。这在处理字节流、协议解析时特别有用。我曾经写过一个串口驱动,就是用char*逐字节解析数据帧的。
3. 指向浮点型的指针
float*和double*,用法和int*类似,但有个坑——浮点数的内存表示不是直观的数值。你如果直接看内存里的二进制,很难看出它代表的是3.14还是-0.001。
float f = 3.14f;
float *pf = &f;
printf("%f\n", *pf); // 输出3.140000
看起来很简单对吧?但如果你用int*去读float的地址,得到的就是一个完全不同的数。我记得有一次调试一个传感器数据,就是因为类型转换错误,读出来的值完全不对。折腾了半天才发现是指针类型搞混了。
小技巧:如果你确实需要把float的二进制表示读出来,用memcpy或者union,不要直接做指针类型转换。这样更安全,也更容易理解。
4. void* 指针
void*是C语言里的“万能指针”。它可以指向任何类型的数据。但有个限制——你不能直接解引用void*,因为编译器不知道它指向的是什么类型。
int a = 10;
void *vp = &a;
// printf("%d\n", *vp); // 错误!不能解引用void*
printf("%d\n", *(int*)vp); // 正确,先转成int*
void*最常见的用途,就是写通用函数。比如标准库里的memcpy、qsort,参数都是void*。这样不管你是传int数组还是float数组,都能用同一个函数。
我个人习惯在写回调函数或通用接口时,用void*来传递用户数据。但要注意,void*虽然灵活,也容易掩盖类型错误。我曾经在项目里因为void*用得太随意,导致一个bug查了两天——传进去的是int*,取出来时当成char*用了,数据全乱。
避坑指南:void*用之前一定要转回正确的类型。如果拿不准类型,宁可加个调试断言,也不要盲目转换。
5. NULL指针与野指针
这两个概念,是C语言新手最容易踩的坑。我当年也栽过不少跟头。
NULL指针,就是指向地址0的指针。它表示“这个指针不指向任何有效对象”。标准做法是:
int *p = NULL;
if (p != NULL) {
*p = 10; // 安全
}
为什么要检查NULL?因为对NULL指针解引用,程序会崩溃。这其实是好事——崩溃总比数据静默损坏强。我在嵌入式项目里,经常用NULL指针来标记“未初始化”或“资源不可用”。
野指针,就是指向随机地址的指针。它比NULL更危险,因为你不知道它指向哪里。解引用野指针,可能崩溃,可能写坏数据,也可能一切正常——这种“随机性”最可怕。
野指针通常有三种来源:
- 指针未初始化:
int *p; *p = 10;—— p指向哪里?不知道。 - 指针指向已释放的内存:
free(p); *p = 10;—— 内存已经还给系统了。 - 指针越界:
int arr[5]; int *p = arr + 10;—— 跑到数组外面去了。
我曾经... 在一个产品代码里,因为忘记把释放后的指针置为NULL,导致后续代码误以为指针仍然有效。结果数据被写到了随机地址,系统偶尔死机。查了整整一周才定位到问题。从那以后,我每次free之后都会立刻把指针置为NULL。
避免野指针的黄金法则:
- 声明指针时立即初始化,要么指向有效对象,要么赋NULL。
- free之后立即把指针置为NULL。
- 不要返回局部变量的地址。
- 指针运算时注意边界,不要越界。
知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以看到,指针的类型决定了它的“步长”和“解引用方式”,而void*是类型系统的“粘合剂”,NULL和野指针则是我们时刻要警惕的“陷阱”。
指针的类型,决定了它的行为。int*、char*、float*各有各的步长和用途。void*是万能钥匙,但用之前得先配好锁。NULL指针是你的安全网,野指针则是你永远不想碰到的雷区。记住这些,指针就不再是洪水猛兽了。
最后说一句:写代码时,每次看到指针,都问自己三个问题——它指向哪里?它是什么类型?它是否有效?养成这个习惯,能帮你避开90%的指针相关bug。
公众号:蓝海资料掘金营,微信deep3321