📜  C中的NULL指针

📅  最后修改于: 2021-05-25 19:01:07             🧑  作者: Mango

在非常高的层次上,我们可以将NULL视为空指针,C中出于各种目的使用了NULL指针。 NULL的一些最常见用例是
a)在尚未为该指针变量分配任何有效内存地址时初始化该指针变量。
b)在访问任何指针变量之前检查空指针。这样,我们可以在与指针相关的代码中执行错误处理,例如,仅当其不为NULL时才取消引用指针变量。
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);

应该注意的是,NULL指针与未初始化和悬空的指针不同。在特定的程序上下文中,所有未初始化或悬空的指针或NULL指针都是无效的,但NULL是C标准中提到的具有特定用途的特定无效指针。我们的意思是未初始化和悬空的指针是无效的,但它们可以指向某个内存地址,该内存地址可能通过内存访问是意外的。

#include 
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;
}

通过专门提到NULL指针,C标准提供了C程序员可以使用的机制,并可以检查给定的指针是否合法。但是NULL到底是什么以及它是如何定义的呢?严格来讲,NULL扩展为实现定义的空指针常量,该常量在许多头文件(例如“ stdio.h ”,“ stddef.h ”,“ stdlib.h ”等)中定义。指针。根据C11标准条款6.3.2.3,

值为0的整数常量表达式,或强制类型为void *的表达式,称为空指针常量。如果将空指针常量转换为指针类型,则保证生成的指针(称为空指针)将不相等的值与指向任何对象或函数的指针进行比较。

在继续进行这个NULL讨论:)之前,让我们先介绍几行有关C标准的代码,以防万一您想参考它进行进一步研究。请注意,ISO / IEC 9899:2011是C语言的最新标准,于2011年12月发布。这也称为C11标准。为了完整起见,让我们提到C的先前标准是C99,C90(也称为ISO C)和C89(也称为ANSI C)。尽管可以从ISO购买实际的C11标准,但仍有一份文档草案可在公共领域免费获得。

进入我们的讨论,在大多数C编译器实现的头文件中,NULL宏定义为((void *)0) 。但是C标准说0也是一个空指针常量。这意味着按照标准,以下内容也是完全合法的。

int * ptr = 0;

请注意,以上C语句中的0在指针上下文中使用,与整数0不同。这是为什么首选使用NULL的原因之一,因为它使代码在代码中明确表明程序员使用的是空指针,而不是整数0。关于NULL的另一个重要概念是“ NULL扩展为实现定义的空指针常量” 。此声明也来自C11第7.19条。这意味着空指针的内部表示可以是非零的位模式,以传达NULL指针。这就是为什么NULL始终不需要在内部表示为全零位模式的原因。编译器实现可以选择将“空指针常量”表示为全1或其他任何形式的位模式。但是,再次作为C程序员,我们无需担心空指针的内部值,除非我们参与了Compiler编码甚至在编码级别以下。话虽如此,通常将NULL表示为仅将所有位设置为0。要在特定平台上了解这一点,可以使用以下内容

#include
int main()
{
 printf("%d",NULL);
 return 0;
}

最有可能的是,它打印0,这是典型的内部null指针值,但是它又会根据C编译器/平台的不同而有所不同。您可以在上述程序中尝试一些其他操作,例如printf(“’%c”,NULL)printf(“%s”,NULL)甚至是printf(“%f”,NULL) 。这些输出将根据所使用的平台而有所不同,但是特别有趣的是将%f与NULL一起使用!

我们可以在C中的NULL上使用sizeof()运算符吗?好吧,允许使用sizeof(NULL) ,但是确切的大小取决于平台。

#include
int main()
{
 printf("%lu",sizeof(NULL));
 return 0;
}

由于NULL被定义为((void *)0) ,我们可以将NULL视为特殊的指针,并且其大小将等于任何指针。如果平台的指针大小为4个字节,则上述程序的输出为4。但是,如果平台上的指针大小为8个字节,则上述程序的输出为8。

取消对NULL的引用呢?如果我们使用以下C代码,将会发生什么

#include
int main()
{
 int * ptr = NULL;
 printf("%d",*ptr);
 return 0;
}

在某些机器上,上述程序可以成功编译,但在程序运行时崩溃,而不必在所有机器上都显示相同的行为。同样,它取决于许多因素。但是提到上述片段的想法是,在访问它之前,我们应该始终检查NULL。

由于通常将NULL定义为((void *)0) ,所以让我们也讨论一下void类型。根据C11标准第6.2.5节,“ void类型包括一组空值;它是无法完成的不完整对象类型”。甚至C11条款6.5.3.4都提到:“ sizeof运算符不应应用于具有函数类型或不完整类型的表达式,该类型的括号名称或指定位字段成员的表达式。 ”基本上,这意味着void是一个不完整的类型,其大小在C程序中没有任何意义,但是实现(例如gcc)可以将sizeof(void)选择为1,以便可以将void指针指向的平面内存视为无类型的内存,即字节序列。但是以下内容的输出不必在所有平台上都相同。

#include
int main()
{
 printf("%lu",sizeof(void));
 return 0;
}

在gcc上,以上代码将输出1。sizeof(void *)呢? C11在这里提到了准则。从第6.2.5节开始,“指向void的指针应与指向字符类型的指针具有相同的表示和对齐要求”。这就是为什么以下命令的输出与计算机上任何指针大小相同的原因。

#include
int main()
{
 printf("%lu",sizeof(void *));
 return 0;
}

尽管上面提到了机器相关的内容,但作为C程序员,我们应该始终努力使我们的代码尽可能地可移植。因此,我们可以得出关于NULL的结论,如下所示:

1.始终将指针变量初始化为NULL。
2.在访问任何指针之前,请始终执行NULL检查。

如果您发现以上有用,请执行点赞/推文/ G + 1。另外,请留下我们发表评论以进一步澄清或提供信息。我们很乐意帮助和学习🙂

想要从精选的最佳视频中学习和练习问题,请查看《基础知识到高级C的C基础课程》。