在非常高的层次上,我们可以将NULL视为一个NULL指针,它在C中用于各种用途。NULL的一些最常见的用例是 a) 在指针变量尚未分配任何有效内存地址时初始化该指针变量。 b) 在访问任何指针变量之前检查空指针。通过这样做,我们可以在指针相关的代码中执行错误处理,例如,仅当指针变量不为空时才取消引用指针变量。 c) 当我们不想传递任何有效的内存地址时,向函数参数传递空指针。
一个例子是
int * pInt = NULL; |
b的例子是
if (pInt != NULL) /*We could use if(pInt) as well*/ { /*Some code*/ } else { /*Some code*/ } |
c的例子是
int fun( int *ptr) { /*Fun specific stuff is done with ptr here*/ return 10; } fun(NULL); |
应该注意的是,空指针不同于未初始化的悬空指针。在特定的程序上下文中,所有未初始化的、悬空的或空指针都是无效的,但空指针是一个特定的无效指针,它在C标准中提到,并且有特定的用途。我们的意思是,未初始化和悬空的指针是无效的,但它们可以指向一些内存地址,这些地址可能是通过非故意的内存访问来访问的。
#include <stdio.h> int main() { int *i, *j; int *ii = NULL, *jj = NULL; if (i == j) { printf ( "This might get printed if both i and j are same by chance." ); } if (ii == jj) { printf ( "This is always printed coz ii and jj are same." ); } return 0; } |
通过特别提到空指针,C标准给出了一种机制,C程序员可以使用它来检查给定的指针是否合法。但是什么是NULL,它是如何定义的呢?严格来说,NULL扩展为实现定义的NULL指针常量,该常量在许多头文件中定义,例如“ 斯特迪奥。H ”, “ stddef。H ”, “ stdlib。H “等等,让我们看看C标准对空指针的看法。根据C11标准第6.3.2.3条,
“ 值为0的整型常量表达式或转换为void*类型的此类表达式称为空指针常量。如果将空指针常量转换为指针类型,则生成的指针(称为空指针)保证与指向任何对象或函数的指针进行不等比较。 ”
在我们进一步讨论这一问题之前,让我们先介绍一下C标准的几行代码,以防你们想进一步研究它。请注意,ISO/IEC 9899:2011是C语言的最新标准,于2011年12月发布。这也被称为C11标准。为了完整起见,让我们提到之前的C标准是C99、C90(也称为ISO C)和C89(也称为ANSI C)。虽然实际的C11标准可以从ISO购买,但有一份草案文件可以在公共领域免费获得。
在我们的讨论中,空宏的定义是 ((void*)0) 在大多数C编译器实现的头文件中。但是C标准表示0也是一个空指针常量。这意味着,根据标准,以下内容也是完全合法的。
int * ptr = 0; |
请注意,上述C语句中的0用于指针上下文,它不同于作为整数的0。这就是为什么首选使用NULL的原因之一,因为它使程序员在代码中明确使用NULL指针,而不是整数0。关于NULL的另一个重要概念是“ NULL扩展为实现定义的NULL指针常量 ”. 本声明也来自C11第7.19条。这意味着空指针的内部表示可以是非零位模式来传递空指针。这就是为什么NULL不必在内部表示为全零位模式。编译器实现可以选择将“空指针常量”表示为所有1或任何其他的位模式。但同样,作为一名C程序员,我们不需要太担心空指针的内部值,除非我们参与了编译器编码,甚至低于编码级别。话虽如此,通常NULL表示为仅设置为0的所有位。要在特定平台上了解这一点,可以使用以下方法
#include<stdio.h> int main() { printf ( "%d" ,NULL); return 0; } |
最有可能的是,它正在打印0,这是典型的内部空指针值,但它也可能因C编译器/平台而异。你可以在上面的程序中尝试其他一些东西,比如 printf(“%c”,空) 或 printf(“%s”,空) 甚至 printf(“%f”,空) .根据所使用的平台,它们的输出会有所不同,但这会很有趣,尤其是 %f 空的!
我们能用吗 sizeof() C中的空运算符?嗯,使用 sizeof(空) 允许,但具体尺寸取决于平台。
#include<stdio.h> int main() { printf ( "%lu" , sizeof (NULL)); return 0; } |
因为NULL被定义为 ((void*)0) ,我们可以将NULL视为一个特殊指针,它的大小将等于任何指针。如果平台的指针大小为4字节,则上述程序的输出将为4字节。但如果平台上的指针大小为8字节,则上述程序的输出将为8字节。
取消对NULL的引用呢?如果我们使用下面的C代码会发生什么
#include<stdio.h> int main() { int * ptr = NULL; printf ( "%d" ,*ptr); return 0; } |
在某些机器上,上面的代码可以成功编译,但当程序运行时会崩溃。它不需要在所有机器上显示相同的行为。同样,这取决于很多因素。但提到上述代码片段的想法是,在访问它之前,我们应该始终检查NULL。
因为NULL通常定义为 ((void*)0) ,让我们来讨论一下 无效的 还要打字。根据C11标准第6.2.5条 void类型包含一组空值;它是一个不完整的对象类型,无法完成 ”.甚至C11第6.5.3.4条也提到 sizeof运算符不得应用于具有函数类型或不完整类型的表达式、此类类型的括号名称或指定位字段成员的表达式。 “基本上,这意味着 无效的 是一种不完整的类型,其大小在C程序中没有任何意义,但实现(如gcc)可以选择 sizeof(无效) 作为1,以便void指针指向的平面内存可以被视为非类型内存,即字节序列。但是,在所有平台上,下面的输出不必相同。
#include<stdio.h> int main() { printf ( "%lu" , sizeof ( void )); return 0; } |
在gcc上,上述输出为1。那么…怎么样 sizeof(void*) ? 这里C11提到了指导原则。根据第6.2.5条 指向void的指针应与指向字符类型的指针具有相同的表示和对齐要求 ”. 这就是为什么下面的输出与机器上的任何指针大小相同。
#include<stdio.h> int main() { printf ( "%lu" , sizeof ( void *)); return 0; } |
尽管上面提到了依赖机器的东西,但作为C程序员,我们应该始终努力使代码尽可能可移植。因此,我们可以对NULL得出如下结论:
1.始终将指针变量初始化为NULL。 2.在访问任何指针之前,始终执行空检查。
如果你觉得以上内容有用,请点击Like/Tweet/G+1。此外,请留下我们的评论,以进一步澄清或信息。我们很乐意帮助和学习