1. 主页
  2. 文档
  3. C语言教程
  4. C语言指针
  5. C中的空指针

C中的空指针

在非常高的层次上,我们可以将 NULL 视为在 C 中用于各种目的的空指针。NULL 的一些最常见用例是
a) 在指针变量尚未分配任何有效内存地址时初始化指针变量。
b) 在访问任何指针变量之前检查空指针。通过这样做,我们可以在指针相关代码中执行错误处理,例如仅当指针变量不为 NULL 时才取消引用指针变量。
c) 当我们不想传递任何有效的内存地址时,将空指针传递给函数参数。

a的例子是

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 扩展为实现定义的空指针常量,该常量在许多头文件中定义,例如“ stdio.h ”、“ stddef.h ”、“ stdlib.h ”等。让我们看看 C 标准对 null 的看法指针。从 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 宏在大多数 C 编译器实现的头文件中定义为((void *)0) 。但是 C 标准说 0 也是一个空指针常量。这意味着按照标准,以下内容也是完全合法的。

int * ptr = 0;

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

#include<stdio.h>
int main()
{
printf("%d",NULL);
return 0;
}

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

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

#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),所以让我们也讨论一下void类型。根据 C11 标准第 6.2.5 条,“ void 类型包含一组空值;它是一个不完整的对象类型,无法完成”。甚至 C11 第 6.5.3.4 条也提到“ sizeof 运算符不得应用于具有函数类型或不完整类型的表达式、此类类型的括号名称或指定位字段成员的表达式。” 基本上,这意味着void是一种不完整的类型,其大小在 C 程序中没有任何意义,但实现(例如 gcc)可以选择sizeof(void)为 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. 在访问任何指针之前始终执行 NULL 检查。

这篇文章对您有用吗?