在非常高的层次上, 我们可以将NULL视为null指针, 它在C中用于各种目的。 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 <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;
}
通过专门提到NULL指针, C标准提供了C程序员可以使用的机制, 并可以检查给定的指针是否合法。但是NULL到底是什么以及它是如何定义的?严格来说, NULL扩展为实现定义的空指针常量, 该常量在许多头文件中定义, 例如"标准版", "stddef.h", "标准库等。让我们看看C标准关于空指针的说法。根据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标准, 但仍可以在公共领域免费获得一份文档草案。
进入我们的讨论, NULL宏定义为((无效*)0)在大多数C编译器实现的头文件中。但是C标准说0也是一个空指针常量。这意味着按照标准, 以下内容也是完全合法的。
int * ptr = 0;
请注意, 以上C语句中的0用于指针上下文, 它与0的整数不同。这是为什么首选使用NULL的原因之一, 因为它使代码在代码中明确表明程序员使用的是空指针, 而不是整数0。关于NULL的另一个重要概念是"NULL扩展为实现定义的空指针常量"。该声明也来自C11第7.19条。这意味着空指针的内部表示可以是非零的位模式, 以传达NULL指针。因此, 始终不需要在内部将NULL表示为全零位模式。编译器实现可以选择将"空指针常量"表示为全1或其他形式的位模式。但是, 再次, 作为C程序员, 除非我们参与了Compiler编码甚至低于编码级别, 否则我们不必担心null指针的内部值。话虽如此, 通常将NULL表示为仅将所有位设置为0。要在特定平台上了解这一点, 可以使用以下内容
#include<stdio.h>
int main()
{
printf ( "%d" , NULL);
return 0;
}
最有可能的是, 它打印0, 这是典型的内部空指针值, 但是它又会根据C编译器/平台的不同而有所不同。你可以尝试上述程序中的其他一些操作, 例如printf("'%c", NULL)orprintf("%s", NULL)乃至printf("%f", NULL)。这些输出将根据所使用的平台而有所不同, 但是特别有趣的是使用%fNULL!
我们可以用吗sizeof()运算符在C中为NULL?好吧, 用法sizeof(NULL)是允许的, 但具体大小取决于平台。
#include<stdio.h>
int main()
{
printf ( "%lu" , sizeof (NULL));
return 0;
}
由于NULL被定义为((无效*)0), 我们可以将NULL视为特殊的指针, 并且它的大小将等于任何指针。如果平台的指针大小为4个字节, 则上述程序的输出为4。但是, 如果平台上的指针大小为8个字节, 则上述程序的输出为8。
取消对NULL的引用呢?如果我们使用以下C代码会发生什么
#include<stdio.h>
int main()
{
int * ptr = NULL;
printf ( "%d" , *ptr);
return 0;
}
在某些计算机上, 以上代码可以成功编译, 但是在运行程序时崩溃, 并且不一定在所有计算机上都显示相同的行为。同样, 它取决于许多因素。但是提到上述片段的想法是, 在访问它之前, 我们应该始终检查NULL。
由于NULL通常定义为((无效*)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(无效*)? C11在这里提到了准则。在第6.2.5节中, "指向void的指针应具有与字符类型的指针相同的表示和对齐要求"。因此, 以下命令的输出将与计算机上的任何指针大小相同。
#include<stdio.h>
int main()
{
printf ( "%lu" , sizeof ( void *));
return 0;
}
尽管上面提到了机器相关的内容, 但作为C程序员, 我们应该始终努力使我们的代码尽可能地可移植。因此, 我们可以得出关于NULL的结论, 如下所示:
1.始终将指针变量初始化为NULL。
2.在访问任何指针之前, 请始终执行NULL检查。
如果你发现以上有用, 请执行点赞/推文/ G + 1。另外, 请留下我们发表评论以进一步澄清或提供信息。我们很乐意帮助和学习🙂