对于空(null)指针与 NULL 指针,相信许多读者对它们之间的关系都很迷惑,甚至有很大一部分读者会认为它们根本就是一回事。其实不然,它们之间存在着一定的本质区别,下面就来详细阐述它们之间的不同。
对于空(null)指针的概念,在 C 标准中明确地定义:值为 0 的整型常量表达式,或强制(转换)为“void*”类型的此类表达式,称为空指针常量。当将一个空指针常量赋予一个指针或与指针作比较时,将把该常量转换为指向该类型的指针,这样的指针称为空指针。空指针在与指向任何对象或函数的指针作比较时保证不会相等。
根据上面的定义,我们可以对空指针做如下几点剖析:
1) 每一种指针类型都有一个空指针,它与同类型的其他所有指针值都不相同。
2) 由系统保证空指针不指向任何实际的对象或函数,也就是说,任何对象或者函数的地址都不可能是空指针,空指针与任何对象或函数的指针值都不相等。因此,取地址操作符 & 永远也不能得到空指针,同样对 malloc() 函数的成功调用也不会返回空指针,但如果调用失败,则 malloc() 函数返回空指针。
3) 空指针表示“未分配”或者“尚未指向任何地方”。它与未初始化的指针有所不同,空指针可以确保不指向任何对象或函数,而未初始化指针可能指向任何地方。
4) 0、0L、'\0'、3-3、0*17以及(void*)0等都是空指针常量,则:
指针变量 p 经过上面任何一种赋值操作之后都将成为一个空指针。至于编译时系统究竟选取哪种形式作为空指针常量使用,则与具体实现相关。在一般情况下,对于 C 语言系统,选择“(void*)0”或 0 的居多(也有个别的选择 0L);而对于 C++ 语言系统,由于存在严格的类型转化的要求,“void*”不能像在 C 语言中那样自由转换为其他指针类型,所以通常只选 0 作为空指针常量,而不选择“(void*)0”。
5) 对于空指针究竟指向内存的什么地方,在标准中并没有明确规定。也就是说,用哪个具体的地址值(0 地址还是某一特定地址)来表示空指针完全取决于系统的实现。在一般情况下,空指针指向 0 地址,即空指针的内部用全 0 来表示,也可以称它为零空指针。当然,也有一些系统用一些特殊的地址值或特殊的方式来表示空指针,也可以称它为非零空指针。
但在实际编程中,我们并不需要了解在系统上的空指针到底是一个零空指针还是一个非零空指针。而我们仅仅只需要知道一个指针是否是空指针就可以了,编译器会自动实现其中的转换,为我们屏蔽其中的实现细节。因此,千万不要把空指针的内部表示等同于整数0的对象表示,有时它们是不同的。
在了解空指针的概念之后,下面来看 NULL 指针。
作为一种良好的编程习惯,很多程序员都不愿意在程序中到处出现未加修饰的 0 或者其他空指针常量。为了让程序中的空指针使用更加明确,从而保持统一的编程风格,标准 C 专门定义了一个标准预处理宏 NULL,其值为“空指针常量”,通常是 0 或者“((void*)0)”,即在指针上下文中的 NULL 与 0 是等价的,而未加修饰的 0 也是完全可以接受的。如在 VC++ 中定义预处理宏 NULL 的代码如下:
这里需要说明的是,当 NULL 定义为“((void *)0)”时,即 NULL 是可以赋值给任何类型指针的值,它的类型为 void*,而不是整数 0,因此初始化“FILE*fp=NULL;”是完全合法的。
而为了区分整数 0 和空指针 0,当需要其他类型的 0 时,即使可能工作,也不能使用 NULL,因为这样处理其格式是错误的,这种类型在非指针上下文中是不能工作的。特别需要注意的是,不能在需要 ASCII 空字符(NUL)的地方使用 NULL。如果确实需要,则可以自定义为:
由此可见,常数 0 是一个空指针常量,而 NULL 仅仅是它的一个别名。NULL 可以确保是 0,但空(null)指针却不一定。