解析C语言中空指针、空指针常量、NULL & 0的详解

什么是空指针常量(null pointer constant)?

[6.3.2.3-3] An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

这里告诉我们:0、0L、'\0'、3 - 3、0 * 17 (它们都是“integer constant expression”)以及 (void*)0 (tyc: 我觉得(void*)0应该算是一个空指针吧,更恰当一点)等都是空指针常量(注意 (char*) 0 不叫空指针常量,只是一个空指针值)。至于系统选取哪种形式作为空指针常量使用,则是实现相关的。一般的 C 系统选择 (void*)0 或者 0 的居多(也有个别的选择 0L);至于 C++ 系统,由于存在严格的类型转化的要求,void* 不能象 C 中那样自由转换为其它指针类型,所以通常选 0 作为空指针常量(tyc: C++标准推荐),而不选择 (void*)0。

什么是空指针(null pointer)?

[6.3.2.3-3] If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

因此,如果 p 是一个指针变量,则 p = 0;、p = 0L;、p = '\0';、p = 3 - 3;、p = 0 * 17; 中的任何一种赋值操作之后(对于 C 来说还可以是 p = (void*)0;), p 都成为一个空指针,由系统保证空指针不指向任何实际的对象或者函数。反过来说,任何对象或者函数的地址都不可能是空指针。(tyc: 比如这里的(void*)0就是一个空指针。把它理解为null pointer还是null pointer constant会有微秒的不同,当然也不是紧要了)

什么是 NULL?

[6.3.2.3-Footnote] The macro NULL is defined in <stddef.h> (and other headers) as a null pointer constant

即 NULL 是一个标准规定的宏定义,用来表示空指针常量。因此,除了上面的各种赋值方式之外,还可以用 p = NULL; 来使 p 成为一个空指针。(tyc:很多系统中的实现:#define NULL (void*)0,与这里的“a null pointer constant”并不是完全一致的)

空指针(null pointer)指向了内存的什么地方(空指针的内部实现)?

标准并没有对空指针指向内存中的什么地方这一个 问题作出规定,也就是说用哪个具体的地址值(0x0 地址还是某一特定地址)表示空指针取决于系统的实现。我们常见的空指针一般指向 0 地址,即空指针的内部用全 0 来表示(zero null pointer,零空指针);也有一些系统用一些特殊的地址值或者特殊的方式表示空指针(nonzero null pointer,非零空指针),具体请参见C FAQ。

幸运的是,在实际编程中不需要了解在我们的系统上空指针到底是一个 zero null pointer 还是 nonzero null pointer,我们只需要了解一个指针是否是空指针就可以了——编译器会自动实现其中的转换,为我们屏蔽其中的实现细节。注意:不要把空指针的内部表示等同于整数 0 的对象表示——如上所述,有时它们是不同的。

如何判断一个指针是否是一个空指针?

这可以通过与空指针常量或者其它的空指针的比较来实现(注意与空指针的内部表示无关)。例如,假设 p 是一个指针变量,q 是一个同类型的空指针,要检查 p 是否是一个空指针,可以采用下列任意形式之一——它们在实现的功能上都是等价的,所不同的只是风格的差别。

指针变量 p 是空指针的判断:
if ( p == 0 )
if ( p == '\0' )
if ( p == 3 - 3 )
if ( p == NULL ) /* 使用 NULL 必须包含相应的标准库的头文件 */
if ( NULL == p )
if ( !p ) <---------------(这里和下面的if(p)都被专门说过一次,null定义不一定是0.这里如果贸然使用!p会很危险的.所以这种写法不能被提倡)
if ( p == q )
...

指针变量 p 不是空指针的判断:
if ( p != 0 )
if ( p != '\0' )
if ( p != 3 - 3 )
if ( p != NULL ) /* 使用 NULL 必须包含相应的标准库的头文件 */
if ( NULL != p )
if ( p )
if ( p != q )
...
可以用 memset 函数来得到一个空指针吗?

这个问题等同于:如果 p 是一个指针变量,那么

memset( &p, 0, sizeof(p) ); 和 p = 0;

是等价的吗?

答 案是否定的,虽然在大多数系统上是等价的,但是因为有的系统存在着“非零空指针” (nonzero null pointer),所以这时两者不等价。由于这个原因,要注意当想将指针设置为空指针的时候不应该使用 memset,而应该用空指针常量或空指针对指针变量赋值或者初始化的方法。
可以定义自己的 NULL 的实现吗?兼答"NULL 的值可以是 1、2、3 等值吗?"类似问题

[7.1.3-2] If the program declares or defines an identifier in a context in which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name, the behavior is undefined.

NULL 是标准库中的一个符合上述条件的 reserved identifier (保留标识符)。所以,如果包含了相应的标准头文件而引入了 NULL 的话,则再在程序中重新定义 NULL 为不同的内容是非法的,其行为是未定义的。也就是说,如果是符合标准的程序,其 NULL 的值只能是 0,不可能是除 0 之外的其它值,比如 1、2、3 等。

malloc 函数在分配内存失败时返回 0 还是 NULL?

malloc 函数是标准 C 规定的库函数。在标准中明确规定了在其内存分配失败时返回的是一个 “null pointer”(空指针):

[7.20.3-1] If the space cannot be allocated, a null pointer is returned.

对于空指针值,一般的文档(比如 man)中倾向于用 NULL 表示,而没有直接说成 0。但是我们应该清楚:对于指针类型来说,返回 NULL 和 返回 0 是完全等价的,因为 NULL 和 0 都表示 “null pointer”(空指针)。(tyc:一般系统中手册中都返回NULL,那我们就用NULL吧)

(0)

相关推荐

  • 详解C语言中的符号常量、变量与算术表达式

    C语言中的符号常量 在结束讨论温度转换程序前,我们再来看一下符号常量.在程序中使用 300.20 等类似的"幻数"并不是一个好习惯,它们几乎无法向以后阅读该程序的人提供什么信息,而且使程序的修改变得更加困难.处理这种幻数的一种方法是赋予它们有意义的名字.#define 指令可以把符号名(或称为符号常量)定义为一个特定的字符串: #define 名字 替换文本 在该定义之后,程序中出现的所有在 #define 中定义的名字(既没有用引号引起来,也不是其它名字的一部分)都将用相应的替换文本

  • C语言 常量,变量及数据详细介绍

    一.数据 图片文字等都是数据,在计算机中以0和1存储. (一)分类 数据分为静态数据和动态数据. ①. 静态数据:一些永久性的的数据,一般存储在硬盘中,只要硬盘没坏数据都是存在的.一般以文件的形式存储在硬盘上,电脑关机重启后依然存在. ②. 动态数据:程序运行过程中,动态产生的的临时数据,一般存储在内存中,内存的存储空间一般较小,计算机关闭后这些数据就会被清除.软件或者电脑关闭则这些临时数据会被清除. ③. 静态数据和动态数据可以转换. ④. 注意:为什么不把动态数据存放到硬盘?因为直接访问内存

  • C语言中常量指针与指针常量区别浅析

    常量指针是指--指向常量的指针,顾名思义,就是指针指向的是常量,即,它不能指向变量,它指向的内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变,从而指向另一个常量.指针常量是指--指针本身是常量.它指向的地址是不可改变的,但地址里的内容可以通过指针改变.它指向的地址将伴其一生,直到生命周期结束.有一点需要注意的是,指针常量在定义时必须同时赋初值.注:也有人将这两个名称的定义与含义反过来认为:"指针常量:顾名思义它的中心词是"常量"这是重点

  • c语言全局变量和局部变量问题及解决汇总

    1.局部变量能否和全局变量重名? 答:能,局部会屏蔽全局.要用全局变量,需要使用"::" 局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量.对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内. 2.如何引用一个已经定义过的全局变量? 答:extern 可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全

  • 详解C语言中的常量指针和指针常量

    概述 对于新手来说,指针在c语言里总是一个非常难以理解的概念.在这篇文章中,我们将解释常量指针,指针常量,const pointer to const(ps:楼主以为这可以翻译成指向常量的常量指针)的区别 常量指针 让我们先来理解什么是常量指针.常量指针是指指针指向的地址是常量.换句话说,一旦常量指针指向了一个变量,你不能让该常量指针指向其他变量了 常量指针的声明方法如下: <type of pointer> * const <name of pointer> 常量指针声明示例:

  • C语言中判断int,long型等变量是否赋值的方法详解

    当然,如果你不赋值给局部变量,这样会导致整个程序的崩溃,因为,它的内容被系统指向了垃圾内存.下面我们看一段代码: 复制代码 代码如下: #include <stdio.h>#include <string.h>#include <stdlib.h>int globle_value;int my_sum(int value1, int value2);long my_sub(long value1, long value2);int main(void){ int aut

  • C语言 变量详解及示例代码

    C 变量 变量其实只不过是程序可操作的存储区的名称.C 中每个变量都有特定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上. 变量的名称可以由字母.数字和下划线字符组成.它必须以字母或下划线开头.大写字母和小写字母是不同的,因为 C 是大小写敏感的.基于前一章讲解的基本类型,有以下几种基本的变量类型: 类型 描述 char 通常是一个八位字节(一个字节).这是一个整数类型. int 对机器而言,整数的最自然的大小. float 单精度浮点值. doub

  • 解析C语言中空指针、空指针常量、NULL & 0的详解

    什么是空指针常量(null pointer constant)?[6.3.2.3-3] An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. 这里告诉我们:0.0L.'\0'.3 - 3.0 * 17 (它们都是"integer constant expression")以及 (void*

  • C语言头文件<string.h>函数详解

    目录 1. strlen —— 求字符串长度 1.1 strlen 的声明与用处 1.2 strlen 的用法 1.3 strlen 的模拟实现 2. strcpy —— 字符串拷贝 2.1 strcpy 的声明与用处 2.2 strcpy 的用法 2.3 strcpy 的模拟实现 3. strcmp —— 字符串比较 3.1 strcmp 的声明与用处 3.2 strcmp 的用法 3.3 strcmp 的模拟实现 4. strcat —— 字符串追加 4.1 strcat 的声明与用处 4.

  • Go语言学习教程之结构体的示例详解

    目录 前言 可导出的标识符 嵌入字段 提升 标签 结构体与JSON相互转换 结构体转JSON JSON转结构体 练习代码步骤 前言 结构体是一个序列,包含一些被命名的元素,这些被命名的元素称为字段(field),每个字段有一个名字和一个类型. 结构体用得比较多的地方是声明与数据库交互时需要用到的Model类型,以及与JSON数据进行相互转换.(当然,项目中任何需要多种数据结构组合在一起使用的地方,都可以选择用结构体) 代码段1:声明一个待办事项的Model类型: type Todo struct

  • C语言静态与动态通讯录的实现流程详解

    目录 静态通讯录 contact.h contact.c test.c 动态通讯录 contact.h contact.c qsort.c test.c 本次通讯录的代码已经放到我的Gitee仓库中,感兴趣的小伙伴可以去看看 Gitee 静态通讯录 在我们学习完C语言的结构体.指针以及动态内存管理之后,我们就可以实现一些有意思的小项目了,通过这些小项目可以加深我们对于相关知识的理解. 静态通讯录主要要求有 静态大小,可以记录10个人的信息(大小自己定) 记录的信息如下:名字.性别.年龄.电话.住

  • C语言三子棋的实现思路到过程详解

    目录 一.三子棋小游戏的简单介绍 二.三子棋的思路及代码实现 1.打印游戏菜单 2.选择是否开始游戏 3.创建并且初始化棋盘 3.1.创建棋盘 3.2.初始化棋盘 4.打印格式化棋盘 5.玩家下棋 6.电脑下棋 7.判断是否玩家或者电脑赢 三.整合三子棋游戏代码 game.h game.c test.c 一.三子棋小游戏的简单介绍 要说大家都很熟悉的一个小游戏,三子棋算是其中一个了.相信大家都玩过三子棋小游戏,在这里我还是给大家介绍简单的游戏规则: 一次只能下一个棋子: 玩家下完棋子后,电脑下棋

  • C语言编程C++旋转字符操作串示例详解

    目录 旋转字符串 字符串左旋 题前认知: 暴力移位: 三步翻转: 判断字符串旋转 题前认知 字符串追加判断 旋转字符串 字符串左旋 实现一个函数,可以左旋字符串中的k个字符. 例如: ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 题前认知: 一个字符串如果就定死了.eg:char arr[]="dfdf"什么的那多没意思,一点都没有人机交互的感觉,(虽然现在人机交互适合个体,不适合集群,但也是比死板的定死字符串舒服) 所以字符串得是我们可输入的,才有可玩性,玩的不

  • C语言形参和实参传值和传址详解刨析

    目录 例题 分析 实参与形参 实际参数(实参): 形式参数(形参): 修改 分析 传值和传址 传值调用 传址调用 讲解知识点之前,我们先来做一道题! 例题 写一个函数可以交换两个整形变量的内容 例如: 交换前:20 30 交换后:30 20 题目让我们用函数的方式写 #include <stdio.h> void Swap1(int x, int y) { int z = 0; z = x; x = y; y = z; } int main() { int a = 0; int b = 0;

  • C语言常用库函数的使用及模拟实现详解例举

    目录 1.strlen 1.计数法 2.递归法 3.指针减指针 2.strcpy 3.strcmp 4.strcat 5.strstr 6.strtok 7.字符分类函数 8.memcpy&memmove 9.memcmp 经历了C语言基础篇的学习,让我们来简单了解几个C语言的库函数! 1.strlen 字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包 含 '\0' ). 函数的模拟实现 1.计数法 int my_strlen(dest)

  • C语言动态内存管理malloc柔性数组示例详解

    目录 1.1为什么存在动态内存管理 1.2动态内存管理函数 1.2.1malloc 1.2.2free 1.2.3calloc 1.2.4realloc 1.3动态内存管理函数易错点 1.3.1对NULL指针的解引用操作 1.3.2对动态开辟空间的越界访问 1.3.3对非动态开辟内存使用free释放 1.3.4使用free释放一块动态开辟内存的一部分 1.3.5对同一块动态内存多次释放 1.3.6动态开辟内存忘记释放(内存泄漏) 2.1常见相关笔试题 2.2C/C++语言中的内存开辟 2.3柔性

  • C语言实现顺序表的基本操作的示例详解

    目录 一.认识顺序表 1.线性表 2.顺序表的概念及结构 二.顺序表的基本操作(接口实现) 1.初始化顺序表 2.打印顺序表 3.尾插 4.尾删 5.扩容 6.头插 7.头删 8.任意位置插入 9.任意位置删除 10.查找某个数的位置 三.顺序表演示及代码(含源码) 1.演示效果 2.完整源代码 一.认识顺序表 1.线性表 线性表是n个具有相同特性的数据元素的有限序列,线性表是一种在实际中广泛使用的数据结构,常见的线性表有顺序表.链表.栈.队列.字符串……线性表在逻辑上是线性结构,也就是说是一条

随机推荐