0、0和数值“零”在指针上下文中不是一回事

来源:互联网 发布:优化网站的文件和资源 编辑:程序博客网 时间:2024/04/23 15:14
看林锐博士的《高质量C/CPP编程》附录的试卷,对空指针的判断居然强制要用NULL(如 if(p==NULL) ),后来从这篇文章看到一些东西觉得有点意思。不耐烦看的人看我的归纳:

0、0和数值“零”在指针上下文中不是一回事,0就是空指针,而不一定是“零”
1、用0还是NULL表示空指针是风格问题,而不是对与错的问题。
2、空指针真的有非零的,多是罕见机器。但此时 0 在指针上下文中会自动转为合适的空指针。
3、用 if(p), if(!p) 还是 if(p!=NULL), if(p==NULL) 都完全合法。
4、NULL一般被定义为0或(void*)0
5、0作为函数实参时,为了表示它是空指针,最好把它至于指针上下文中,即加上(char*)或(void*)修饰。(这要看编译器了,我在gcc4.1下就不需要修饰)。

=================================================
原文出处:http://c-faq-chn.sourceforge.net/ccfaq/
部分文摘
=================================================
6.2 怎样在程序里获得一个空指针?
根据语言定义, 在指针上下文中的常数 0 会在编译时转换为空指针。也就是说, 在初始化、赋值或比较的时候, 如果一边是指针类型的值或表达式, 编译器可以确定另一边的常数 0 为空指针并生成正确的空指针值。因此下边的代码段完全合法:

char *p = 0;
if(p != 0)

参见问题 5.3。

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

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

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

如果范围内有函数原型, 则参数传递变为 “赋值上下文", 从而可以安全省略多数类型转换, 因为原型告知编译器需要指针, 使之把未加修饰的 0 正确转换为适当的指针。函数原型不能为变长参数列表中的可变参数提供类型。 (参见问题 15.3) 在函数调用时对所有的空指针进行类型转换可能是预防可变参数和无原型函数出问题的最安全的办法。


============================
6.3 用缩写的指针比较 "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) 这样的 "缩写", 尽管完全合法, 但被一些人认为是不好的风格 (另外一些人认为恰恰是好的风格; 参见问题 17.8)。

参见问题 9.2。

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

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

NULL 只能用作指针常数; 参见问题 5.7。

============================
6.14 说真的, 真有机器用非零空指针吗, 或者不同类型用不同的表达?
至少 PL/I, Prime 50 系列用段 07777, 偏移 0 作为空指针。后来的型号使用段 0, 偏移 0 作为 C 的空指针, 迫使类似 TCNP (测试 C 空指针) 的指令明显地成了现成的作出错误猜想的蹩脚 C 代码。旧些的按字寻址的 Prime 机器同样因为要求字节指针 (char *) 比字指针 (int *) 长而臭名昭著。

Data General 的 Eclipse MV 系列支持三种结构的指针格式 (字、字节和比特指针), C 编译器使用了其中之二:char * 和 void * 使用字节指针, 而其它的使用字指针。

某些 Honeywell-Bull 大型机使用比特模式 06000 作为 (内部的) 空指针。

CDC Cyber 180 系列使用包含环 (ring), 段和位移的 48 位指针。多数用户 (在环 11 上) 使用的空指针为 0xB00000000000。 在旧的 1 次补码的 CDC 机器上用全 1 表示各种数据, 包括非法指针, 是十分常见的事情。

旧的 HP 3000 系列对字节地址和字地址使用不同的寻址模式; 正如上面的机器一样, 它因此也使用不同的形式表达 char * 和 void * 型指针及其它指针。

Symbolics Lisp 机器是一种标签结构, 它甚至没有传统的数字指针; 它使用 <NIL, 0> 对 (通常是不存在的 <对象, 偏移> 句柄) 作为 C 空指针。

根据使用的 ``内存模式", 8086 系列处理器 (PC 兼容机) 可能使用 16 位的数据指针和 32 位的函数指针, 或者相反。

一些 64 位的 Cray 机器在一个字的低 48 位表示 int *; char * 使用高 16 位的某些位表示一个字节在一个字中的偏移。


============================
6.7 如果 NULL 和 0 作为空指针常数是等价的, 那我到底该用哪一个呢?
许多程序员认为在所有的指针上下文中都应该使用 NULL, 以表明该值应该被看作指针。另一些人则认为用一个宏来定义 0, 只不过把事情搞得更复杂, 反而令人困惑。因而倾向于使用未加修饰的 0。没有正确的答案。 (参见问题 9.2 和 17.8) C 程序员应该明白, 在指针上下文中 NULL 和 0 是完全等价的, 而未加修饰的 0 也完全可以接受。任何使用 NULL (跟 0 相对) 的地方都应该看作一种温和的提示, 是在使用指针; 程序员 (和编译器都) 不能依靠它来区别指针 0 和整数 0。

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

#define NUL '/0'
0 0
原创粉丝点击