我依稀记得几年前读过这篇文章,但我在网上找不到任何参考资料。
你能给我一个NULL宏没有扩展到0的例子吗?
为了清晰起见而编辑:今天它扩展为((void *)0)
,(0)
或(0L)
。但是,有些架构早已被遗忘,而这种情况并非如此,并且NULL扩展到了不同的地址。像
#ifdef UNIVAC
#define NULL (0xffff)
#endif
我正在寻找这样一台机器的例子。
更新以解决问题:
我在当前标准的背景下并不是指这个问题,或者用不正确的术语来扰乱人们。但是,我接受的答案证实了我的假设:
后来的模型使用[blah],显然是所有现存的写得不好的C代码的错误,这些代码做出了错误的假设。
有关当前标准中空指针的讨论,请参阅this question。
答案 0 :(得分:32)
C FAQ包含一些非0表示的历史机器的例子。
来自The C FAQ List,question 5.17:
问:说真的,有任何实际的机器真的使用非零null 指针,或指向不同指针的不同表示 类型?
答:Prime 50系列使用了段07777,偏移量为0 指针,至少对于PL / I。后来的模型使用了段0,偏移0为 C中的空指针,需要新的指令,如TCNP(测试 C Null Pointer),显然是所有现存的[脚注] 写得不好的C代码做出了错误的假设。年纪大了, 有文字说服的Prime机器也因要求更大而臭名昭着 字节指针(char *的)比字指针(int *的)。
Data General的Eclipse MV系列有三个架构 支持指针格式(字,字节和位指针),其中两个 由C编译器使用:char *和void *的字节指针,以及word 其他一切的指针。由于历史原因 来自16位Nova线的32位MV线的演变 指针和字节指针有偏移,间接和环 保护位在单词的不同位置。传递不匹配 函数的指针格式导致保护错误。 最终,MV C编译器添加了许多兼容性选项来尝试 处理具有指针类型不匹配错误的代码。
一些Honeywell-Bull主机使用位模式06000 (内部)空指针。
CDC Cyber 180系列有48位指针,由一个环组成, 段和偏移量。大多数用户(在第11环中)都有空指针 0xB00000000000。在旧的CDC补充机器上很常见 使用全一位字作为各种数据的特殊标志, 包括无效地址。
旧的HP 3000系列使用不同的字节寻址方案 地址而不是字地址;像上面的几台机器一样 因此它对char *和void *使用不同的表示形式 指针比其他指针。
Symbolics Lisp Machine,一个标记的架构,甚至没有 传统的数字指针;它使用这对(基本上是一个 不存在的句柄)作为C空指针。
取决于所使用的“内存模型”,8086系列处理器(PC 兼容性)可以使用16位数据指针和32位函数 指针,反之亦然。
一些64位Cray机器在a的低48位表示int * 字; char *另外使用高16位中的一些来表示a 一个字内的字节地址。
答案 1 :(得分:3)
在C编译器中,它可以扩展为“((void *)0)
”(但不必这样做)。这对C ++编译器不起作用。
另见C FAQ,其中有null pointers的整章。
答案 2 :(得分:3)
很久以前,当它被输入为((void*)0)
或其他一些特定于机器的方式时,该机器没有使用全零位模式。
某些平台(某些CDC或Honeywell机器)具有不同的NULL位模式(即,不全部为零),尽管ISO / ANSI修复了在C90批准之前,通过指定{{1在源代码中是正确的NULL指针,无论基础位模式的。从0
开始(尽管如上所述,这个措辞一直追溯到C90):
值为
C11 6.3.2.3 Pointers /4
的整型常量表达式,或者类型为0
的表达式,称为空指针常量。
答案 3 :(得分:2)
在GNU libio.h文件中:
#ifndef NULL
# if defined __GNUG__ && \
(__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8))
# define NULL (__null)
# else
# if !defined(__cplusplus)
# define NULL ((void*)0)
# else
# define NULL (0)
# endif
# endif
#endif
请注意__cplusplus上的条件编译。 C ++不能使用((void *)0),因为它有更严格的关于指针转换的规则;标准要求NULL为0. C允许其他NULL定义。
答案 4 :(得分:1)
C编译器通常使用((void *)0)
。原因是将NULL
传递给具有可变参数的函数(或者现在很少但仍然是没有原型的合法函数)。当指针大于int时,0
只会被提升为int
,因此无法正确读取指针。
C ++编译器不能使用该定义,因为C ++不允许从void *
进行隐式转换(将0
转换为任何指针是特殊的)。但是,C ++ 11引入了新的关键字nullptr
,它是特殊nullptr_t
类型的空指针常量,可以隐式转换为任何指针类型,但不能转换为数字。这解决了可变参数问题和隐式转换以及过载选择的更严重问题(0
,显而易见的原因是选择{1}}重载超过指针1)。为旧编译器自己定义这些是合法的,并且过去曾尝试过一些C ++编译器。
答案 5 :(得分:0)
NULL
宏扩展为实现定义的空指针常量。它可以是任何东西(因为它是实现定义的),但在指针上下文中,效果始终与扩展为常量0
的效果相同。
除非您将NULL
视为“非0”,否则在0
扩展为特定于(void *) 0
的内容时,标准C历史记录中从未出现过任何时间。但(void *) 0
的{{1}}在今天被广泛使用。
答案 6 :(得分:0)
在现代C中,void *pointer = 0;
意味着将“指针”初始化为不指向任何东西。它是否是特定于平台的,是否通过将“指针”的位设置为全零来实现。
过去,指针上下文中“0”的这种正式含义尚未建立。有必要将指针设置为平台视为“不指向任何地方”的实际值。例如,平台可能会选择一些永远不会将页面映射到它的固定地址。在这种情况下,在旧的编译器中,平台可能已将NULL
定义为:
#define NULL ((void*)0xFFFFF000)
当然,今天,没有理由不将其定义为((void*)0)
。