全新论坛MCU智学网上线,欢迎访问新论坛!稀缺资源、技术干货、参考设计、原厂资料尽在MCU智学网
更新自动建库工具PCB Footprint Expert 2023.13 Pro / Library Expert 破解版

干货分享---c语言void与NULL完全解析

[复制链接]
2925 0

本文包含原理图、PCB、源代码、封装库、中英文PDF等资源

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
c语言void与NULL完全解析
朱有鹏

1、void类型的本质
现在编程语言分2种:强类型语言和弱类型语言。强类型语言中所有的变量都有自己固定的类型,这个类型有固定的内存占用,有固定的解析方法;弱类型语言中没有类型的概念,所有变量全都是一个类型(一般都是字符串的),程序在用的时候再根据需要来处理变量。C语言就是典型的强类型语言,C语言中所有的变量都有明确的类型。因为C语言中的一个变量都要对应内存中的一段内存,编译器需要这个变量的类型来确定这个变量占用内存的字节数和这一段内存的解析方法。
数据类型决定了变量占用的空间大小和内存的解析方法。所以C语言中变量必须有确定的数据类型,如果一个变量没有确定的类型(就是所谓的无类型)会导致编译器无法给这个变量分配内存,也无法解析这个变量对应的内存。因此不可能有没有类型的变量,但是C语言中可以有没有类型的内存。在内存还没有和具体的变量相绑定之前,内存就可以没有类型。实际上纯粹的内存就是没有类型的,内存只是因为和具体的变量相关联后才有了确定的类型(其实内存自己本身是不知道的,而编译器知道,我们程序在使用这个内存时知道类型所以会按照类型的含义去进行内存的读和写)。
void类型的字面上的含义是:不知道类型,不确定类型,或者说还没确定类型,很多初学者把它理解成空的,这是不对的。我们以void a为例,void a定义了一个void类型的变量,含义就是说a是一个变量,而且a肯定有确定的类型,只是目前我还不知道a的类型,所以标记为void类型。在C语言中如果我们去定义一个void a; a = 5;去执行它必然出错,因为a还没确定具体数据类型,我们无法给它分配内存空间,故而出错。
那我们什么情况下需要void类型呢?其实就是在描述一段还没有具体使用的内存时需要使用void类型。典型应用案例就是malloc的返回值。malloc()函数的原型是void *malloc(size_t size);malloc函数向系统堆管理器申请一段内存给当前程序使用,malloc返回的是一个指针,这个指针指向申请的那段内存。malloc刚申请的这段内存尚未用来存储数据,malloc函数也无法预知这段内存将来被存放什么类型的数据,所以malloc无法返回具体类型的指针,故而返回一个void *类型,告诉外部我返回的是一段干净的内存空间,但尚未确定类型。所以当我们在malloc之后可以给这段内存读写任意类型的数据。
void *类型的指针指向的内存是尚未确定类型的,因此我们后续可以使用强制类型转换强行将其转为各种类型。这就是void类型的最终归宿-被强制类型转换成一个具体类型。
现在我们来看程序8.3
#include <stdio.h>
#include<malloc.h>
int main(void)
{
        int *p = (int *)malloc(sizeof(int));// 由void *强制转为
//int *,不会警告 (1)
        return 0;
}
程序8.3
编译运行程序8.3,没有出错。但当我们把程序8.3中代码(1)写成 int *p = malloc(sizeof(int));,那么程序将无法通过编译报错,因为malloc无法返回具体类型的指针。需要注意的是当使用void类型时一般都是用void *,而不仅仅是使用void,这个是因为void *可以指向任何类型的数据。
2、 C语言中的NULL
2.1 NULL的定义
NULL不是C语言关键字,本质上是一个宏定义,在C/C++中NULL的标准定义是这样的:
#ifdef _cplusplus                        // 条件编译
#define NULL 0
#else
#define NULL (void *)0                // 这里对应C语言
#endif
在C++的编译环境中,编译器预先定义了一个宏_cplusplus,程序中可以用条件编译来判断当前的编译环境是C++的还是C的。在C语言中NULL的本质是0,但是这个0不是当一个数字解析,而是当一个内存地址来解析的,这个0其实是0x00000000,代表内存的0地址。(void *)0这个整体表达式表示一个指针,这个指针变量本身占4字节,地址指向哪里取决于指针变量本身,这个指针变量的值是0,也就是说这个指针变量指向0地址(实际是0地址开始的一段内存)。
假设我们定义一个指针int *p; p是一个函数内的局部变量,则p的值是随机的,也就是说p是一个野指针。如果我们这样定义int *p = NULL; p也是一个局部变量,其分配在栈上的地址是由编译器决定的,我们不必关心,但p的值是(void *)0的值,实际就是0,意思是指指针p指向内存的0地址处。这时候p就不是野指针了。为什么要让一个野指针指向内存地址0处呢?主要是因为在大部分的CPU中,内存的0地址处都不是可以随便访问的(一般都是操作系统严密管控区域,所以应用程序不能随便访问)。所以野指针指向了这个区域可以保证野指针不会造成误伤。如果程序无意识的解引用指向0地址处的野指针则会触发段错误。这样就可以提示你帮助你找到程序中的错误。
标准的指针使用步骤是:
int *p = NULL;                // 定义p时立即初始化为NULL
p = xx;
if (NULL != p)
{
        *p                             // 在确认p不等于NULL的情况下才去解引用p
}
p = NULL                           // 用完之后p再次等于NULL
注意:一般比较一个指针和NULL是否相等不写成if (p == NULL),而写成if (NULL == p)。原因是第一种写法中如果不小心把 ==写成了 = ,则编译器不会报错,但是程序的意思完全不一样了;而第二种写法如果不小心把 == 写成了 = 则编译器会发现并报错。
2.2 '\0' 、 '0' 、 0  和 NULL的区别:
1.'\0'是一个转义字符,它对应的ASCII编码值是0,本质就是0;
2.'0'是一个字符,他对应的ASCII编码值是48,本质是48;
3.0是一个数字,他就是0,本质就是0;
4.NULL是一个表达式,是强制类型转换为void *类型的0,本质是0。
在实际应用中,'\0'用法是C语言字符串的结尾标志,一般用来比较字符串中的字符以判断字符串有没有到头;'0'是字符0,对应0这个字符的ASCII编码,一般用来获取0的ASCII码值;0是数字,一般用来比较一个int类型的数字是否等于0;NULL是一个表达式,一般用来比较指针是否是一个野指针。
                  

举报

回复

230 个评论

*滑块验证:
您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

打开支付宝扫一扫,最高立得1212元红包
搜索

图文热点

更多

社区学堂

更多

客服中心

QQ:187196467 服务时间:周一至周日 8:30-20:30

关注我们

关于我们
关于我们
友情链接
联系我们
帮助中心
网友中心
购买须知
支付方式
服务支持
资源下载
售后服务
定制流程
关注我们
官方微博
官方空间
官方微信
快速回复 返回顶部 返回列表