什么时候NULL宏不是0?

时间:2010-04-08 02:14:59

标签: c null macros history

我依稀记得几年前读过这篇文章,但我在网上找不到任何参考资料。

你能给我一个NULL宏没有扩展到0的例子吗?

为了清晰起见而编辑:今天它扩展为((void *)0)(0)(0L)。但是,有些架构早已被遗忘,而这种情况并非如此,并且NULL扩展到了不同的地址。像

这样的东西
#ifdef UNIVAC
     #define NULL (0xffff)
#endif

我正在寻找这样一台机器的例子。

更新以解决问题:

我在当前标准的背景下并不是指这个问题,或者用不正确的术语来扰乱人们。但是,我接受的答案证实了我的假设:

后来的模型使用[blah],显然是所有现存的写得不好的C代码的错误,这些代码做出了错误的假设。

有关当前标准中空指针的讨论,请参阅this question

7 个答案:

答案 0 :(得分:32)

C FAQ包含一些非0表示的历史机器的例子。

来自The C FAQ Listquestion 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)

C中的

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)