关于空指针

来源:互联网 发布:网络小龙女是什么意思 编辑:程序博客网 时间:2024/05/02 11:09
臭名昭著的空指针到底是什么?
 
语言定义中说明, 每一种指针类型都有一个特殊值 --- "空指针" ---  它与同类型的其它所有指针值都不相同, 它 "与任何对象或函数的指针值都不相等"。也就是说, 取地址操作符 & 永远也不能得到空指针, 同样对 malloc() 的成功调用也不会返回空指针, 如果失败, malloc() 的确返回空指针, 这是空指针的典型用法: 表示 "未分配" 或者 "尚未指向任何地方" 的指针。

空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数; 而未初始化指针则可能指向任何地方。

如上所述, 每种指针类型都有一个空指针, 而不同类型的空指针的内部表示可能不尽相同。尽管程序员不必知道内部值, 但编译器必须时刻明确需要那种空指针, 以便在需要的时候加以区分

怎样在程序里获得一个空指针?
 
根据语言定义, 在指针上下文中的常数 0 会在编译时转换为空指针。也就是说, 在初始化、赋值或比较的时候, 如果一边是指针类型的值或表达式, 编译器可以确定另一边的常数 0 为空指针并生成正确的空指针值。因此下边的代码段完全合法:
    char *p = 0;    if(p != 0)
 

然而, 传入函数的参数不一定被当作指针环境, 因而编译器可能不能识别未加修饰的 0 “表示" 指针。在函数调用的上下文中生成空指针需要明确的类型转换, 强制把 0 看作指针。例如, Unix 系统调用 execl 接受变长的以空指针结束的字符指针参数。它应该如下正确调用:

    execl("/bin/sh", "sh", "-c", "date", (char *)0);

如果省略最后一个参数的 (char *) 转换, 则编译器无从知道这是一个空指针, 从而当作一个 0 传入。(注意很多 Unix 手册在这个例子上都弄错了。)

如果范围内有函数原型, 则参数传递变为 "赋值上下文", 从而可以安全省略多数类型转换, 因为原型告知编译器需要指针, 使之把未加修饰的 0  正确转换为适当的指针。函数原型不能为变长参数列表中的可变参数提供类型。 

 在函数调用时对所有的空指针进行类型转换可能是预防可变参数和无原型函数出问题的最安全的办法。

摘要:

可以使用未加修饰的 0: 需要显示的类型转换: 初始化 函数调用, 作用域内无原型 赋值 变参函数调用中的可变参数 比较   固定参数的函数调用且在作用域内有原型  

 

用缩写的指针比较 "if(p)" 检查空指针是否可靠?

 

如果空指针的内部表达不是 0 会怎么样? 当 C 在表达式中要求布尔值时, 如果表达式等于 0 则认为该值为假, 否则为真。换言之, 只要写出

    if(expr)

无论 "expr" 是任何表达式, 编译器本质上都会把它当

    if((expr) != 0)

处理。

如果用指针 p 代替 "expr" 则

    if(p) 等价于 if(p != 0)。

而这是一个比较上下文, 因此编译器可以看出 0 实际上是一个空指针常数, 并使用正确的空指针值。这里没有任何欺骗; 编译器就是这样工作的, 并为、二者生成完全一样的代码。空指针的内部表达无关紧要

布尔否操作符 ! 可如下描述:

!expr 本质上等价于 (expr)?0:1   或等价于 ((expr) == 0)

从而得出结论

if(!p) 等价于 if(p == 0)

类似 if(p) 这样的 "缩写", 尽管完全合法, 但被一些人认为是不好的风格

NULL 是什么, 它是怎么定义的?

作为一种风格, 很多人不愿意在程序中到处出现未加修饰的 0。因此定义了预处理宏  NULL (在 <stdio.h> 和其它几个头文件中) 为空指针常数, 通常是 0 或者 ((void *)0) 。希望区别整数 0 和空指针 0  的人可以在需要空指针的地方使用 NULL。

使用 NULL 只是一种风格习惯; 预处理器把所有的 NULL 都还原回 0, 而编译还是依照 上文的描述处理指针上下文的 0。特别是, 在函数调用的参数里, NULL 之前 (正如在 0  之前) 的类型转换还是需要。

摘要:

可以使用未加修饰的 0: 需要显示的类型转换: 初始化 函数调用, 作用域内无原型 赋值 变参函数调用中的可变参数 比较   固定参数的函数调用且在作用域内有原型  

的表格对 0 和 NULL 都有效 (带修饰的  NULL 和带修饰的 0 完全等价)。

NULL 只能用作指针常数;

在使用非全零作为空指针内部表达的机器上, NULL 是如何定义的?

 跟其它机器一样: 定义为 0 (或某种形式的 0;

当程序员请求一个空指针时, 无论写 "0" 还是 "NULL", 都是有编译器来生成适合机器的空指针的二进制表达形式。因此, 在空指针的内部表达不为 0 的机器上定义 NULL 为 0 跟在其它机器上一样合法:编译器在指针上下文看到的未加修饰的 0 都会被生成正确的空指针。

如果 NULL 定义成 #define NULL ((char *)0)  难道不就可以向函数传入不加转换的 NULL 了吗?

一般情况下, 不行。复杂之处在于, 有的机器不同类型数据的指针有不同的内部表达。这样的 NULL 定义对于接受字符指针的的函数没有问题, 但对于其它类型的指针参数仍然有问题 (在缺少原型的情况下), 而合法的构造如

    FILE *fp = NULL;

则会失败。

不过, ANSI C 允许 NULL 的可选定义

    #define NULL ((void *)0)

除了潜在地帮助错误程序运行 (仅限于使用同样类型指针的机器, 因此帮助有限) 以外, 这样的定义还可以发现错误使用 NULL 的程序 (例如, 在实际需要使用 ASCII NUL 字符的地方)。

无论如何, ANSI 函数原型确保大多数 (尽管不是全部; )指针参 数在传入函数时正确转换。 因此, 这个问题有些多余。

如果 NULL 和 0 作为空指针常数是等价的, 那我到底该用哪一个呢?

许多程序员认为在所有的指针上下文中都应该使用 NULL, 以表明该值应该被看作指针。另一些人则认为用一个宏来定义 0, 只不过把事情搞得更复杂, 反而令人困惑。因而倾向于使用未加修饰的 0。没有正确的答案。 

 C 程序员应该明白, 在指针上下文中  NULL 和 0 是完全等价的, 而未加修饰的 0 也完全可以接受。任何使用 NULL  (跟 0 相对) 的地方都应该看作一种温和的提示, 是在使用指针; 程序员 (和编译器都) 不能依靠它来区别指针 0 和整数 0。

在需要其它类型的 0 的时候, 即便它可能工作也不能使用 NULL, 因为这样做发出了错误的格式信息。(而且, ANSI 允许把 NULL 定义为 ((void *)0), 这在非指针的上下文中完全无效。特别是, 不能在需要 ASCII 空字符 (NUL) 的地方用 NULL。如果有必要, 提供你自己的定义

    #define NUL '/0'

 

但是如果 NULL 的值改变了, 比如在使用非零内部空指针的机器上, 难道用 NULL (而不是 0) 不是更好吗?

不。(用 NULL 可能更好, 但不是这个原因。) 尽管符号常量经常代替数字使用以备 数字的改变, 但这不是用 NULL 代替 0 的原因。语言本身确保了源码中的 0 (用于指针上下文) 会生成空指针。NULL 只是用作一种格式习惯。

 

这有点奇怪。NULL 可以确保是 0, 但空 (null) 指针却不一定?

随便使用术语 "null" 或 "NULL" 时, 可能意味着以下一种或几种含义:

  1. 概念上的空指针, 定义的抽象语言概念。它使用以下的东西实现的 ……
  2. 空指针的内部 (或运行期) 表达形式, 这可能并不是全零, 而且对不用的指针类型可能不一样。真正的值只有编译器开发者才关心。C 程序的作者永远看不到它们, 因为他们使用 ……
  3. 空指针常数, 这是一个常整数 0 。它通常隐藏在  ……
  4. NULL 宏, 它被定义为 0 ()。最后转移我们注意力到  ……
  5. ASCII 空字符 (NUL), 它的确是全零, 但它和空指针除了在名称上以外, 没有任何必然关系; 而……
  6. "空串" (null string), 它是内容为空的字符串 ("")。在 C 中使用空串这个术语可能令人困惑, 因为空串包括空字符 ('/0'),但 包括空指针, 这让我们绕了一个完整的圈子 ……

本文用短语 ``空指针" (``null pointer", 小写) 表示第一种含义, 标识 ``0"  或短语 ``空指针常数" 表示含义 3, 用大写 NULL 表示含义 4。  

原创粉丝点击