指针的声明与初始化:指针类型、空指针NULL、野指针的避免
指针这东西,说白了就是地址的搬运工。但很多初学者一上来就被它搞晕了。我刚开始学C语言时,也觉得指针神神秘秘的,后来才发现——它不过是个变量,存的是地址而已。
嗯,咱们今天就把指针的声明、初始化,还有那些容易踩的坑,一次性说清楚。
指针的声明:类型决定一切
指针声明时,必须带上类型。为什么?因为编译器需要知道,这个地址里存的是什么类型的数据。
int *p; // p 是一个指向 int 的指针
char *cp; // cp 是一个指向 char 的指针
float *fp; // fp 是一个指向 float 的指针
你看,写法就是 类型 *变量名。这里的 * 是声明符,告诉编译器:我要声明一个指针。
核心要点:指针的类型决定了它能访问多大的内存区域。int* 一次读4个字节,char* 一次读1个字节。搞错了类型,数据就全乱了。
我在项目中遇到过一件事:有人用 char* 去访问一个 int 数组,结果每次只读到一个字节,数据拼接出来完全不对。调试了半天才发现是类型不匹配。
指针的初始化:不给初值就是给自己挖坑
指针声明后,一定要初始化。不初始化的话,它里面存的是一个随机地址——这就是野指针的根源。
int a = 10;
int *p = &a; // 正确:p 指向 a 的地址
int *q; // 错误:q 未初始化,是野指针
*q = 20; // 危险!你根本不知道写到了哪里
我个人习惯是:声明指针的同时就给它赋值。如果暂时没有合适的地址,就赋值为 NULL。
空指针 NULL:一个安全的“占位符”
NULL 在C语言中通常定义为 (void*)0,表示指针不指向任何有效地址。用它来初始化指针,可以避免野指针问题。
int *p = NULL; // 安全:p 不指向任何地方
if (p != NULL) {
*p = 10; // 只有 p 非空时才操作
}
我的建议:每次使用指针前,都检查一下它是不是 NULL。尤其是在嵌入式开发中,硬件地址映射失败时,指针可能变成 NULL,直接解引用就会触发 HardFault。
我曾经在一个项目中,因为没检查 NULL,导致程序在特定条件下直接崩溃。后来加了个 if (ptr != NULL) 的判断,问题就解决了。你想想看,一个简单的判断就能避免系统复位,多划算。
野指针的避免:三个“不要”
野指针是C语言里最常见的坑之一。说白了,就是指针指向了一块“不该碰”的内存。怎么避免?记住这三个原则:
- 不要使用未初始化的指针——声明时赋 NULL 或有效地址
- 不要使用已释放的指针——free 之后立即置 NULL
- 不要越界访问——指针加减时,确保不超出合法范围
int *p = (int*)malloc(sizeof(int) * 10);
if (p != NULL) {
// 使用 p
free(p);
p = NULL; // 释放后置 NULL,避免悬空指针
}
警告:free 之后不置 NULL,指针就成了“悬空指针”。它指向的内存已经被回收,但指针本身还保存着那个地址。如果再解引用,行为是未定义的——可能崩溃,也可能悄无声息地写坏数据。
我曾经在调试一个通信协议栈时,发现数据偶尔会错乱。查了两天才找到原因:一个线程 free 了缓冲区,另一个线程还在用那个指针。后来统一规范:free 后立即置 NULL,再也没出过问题。
指针类型与 void*:万能指针
void* 是一种特殊指针,它可以指向任何类型的数据。但注意,它不能直接解引用——因为编译器不知道它指向的是什么类型。
int a = 100;
void *vp = &a;
// printf("%d\n", *vp); // 错误!不能解引用 void*
printf("%d\n", *(int*)vp); // 正确:先转成 int* 再解引用
void* 在嵌入式开发中很常用,比如 DMA 传输、内存拷贝函数 memcpy 的参数就是 void*。它让函数可以处理任意类型的数据,但使用时一定要小心类型转换。
知识体系图:指针声明与初始化
总结一下
指针的声明与初始化,其实就三件事:
- 声明时带类型——编译器才知道怎么读写
- 初始化给地址或 NULL——别让指针“裸奔”
- 用前检查 NULL——嵌入式开发中尤其重要
我个人觉得,指针这东西,你越怕它,它越容易出问题。把它当成一个普通变量,只不过存的是地址,心里就踏实多了。嗯,今天就聊到这儿,下次咱们聊聊指针的算术运算——那才是真正体现指针魅力的地方。
一句话记住:指针声明看类型,初始化给地址或 NULL,用前检查防崩溃。
公众号:蓝海资料掘金营,微信deep3321