【问题标题】:Why is C++'s NULL typically an integer literal rather than a pointer like in C?为什么 C++ 的 NULL 通常是整数文字而不是 C 中的指针?
【发布时间】:2021-11-02 12:27:26
【问题描述】:

多年来,我一直在编写 C++,使用 nullptr 作为空指针。我也知道 C,NULL 的来源,记住它是空指针的常量,类型为void *

出于某些原因,我不得不在我的 C++ 代码中使用 NULL 来做一些事情。好吧,想象一下当在某些模板参数推导过程中编译器告诉我我的 NULL 真的是……长时,我会感到惊讶。所以,我仔细检查了:

#include <type_traits>
#include <cstddef>

static_assert(not std::is_same<decltype(NULL), long>::value, "NULL is long ???");

事实上,静态断言失败(使用 GCC 和 Clang)。

我查看了cppreference.com,果然(C++11 措辞):

宏NULL是一个实现定义的空指针常量,可以是 一个值为 0 的整型文字,或类型为 std::nullptr_t 的纯右值。

为什么这有意义?就其本身而言,以及鉴于 C 的不兼容性?

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • einpoklum,“我也知道 C,NULL 的起源,记住它是空指针的常量,类型为 void *。”过于狭窄,因为 C 中存在其他可能性。您的 C 引用是什么来支持您肯定是 void * 的断言?
  • @chux-ReinstateMonica:你说的很对。我对 C 语言中 NULL 的假设只是民间传说,这是事实的一部分。

标签: c++ c nullptr null-pointer cross-language


【解决方案1】:

C 并不简单地将NULL 定义为(void *)0。也可以将其定义为 0 或任何对其求值的常量表达式。使用#define NULL 0 可以在任何一种语言中使用。

编辑:this answer 非常好,更详细。

【讨论】:

  • 你说的很对。建议你也链接到this SO answer关于NULL的类型。
【解决方案2】:

vcruntime.h

定义如下:

#ifdef __cplusplus
    #define NULL 0
#else
    #define NULL ((void *)0)

【讨论】:

  • 一个有趣的数据点......但它没有回答“为什么”。
  • @einpoklum 我同意 TobySpeight “因为 C++ 没有 C 的从 void* 到其他指针类型的隐式转换。” 所以写的时候会报错int* p = (void*)0; 但它适用于 void* p = (void*)0;
【解决方案3】:

两种语言中,NULL实现定义的空指针常量。 C++ 第 17.2.3 节,其定义参考 C 7.19。

也就是说,在C中,NULL可以定义为整数类型,也可以定义为void*类型,而在C++中可以定义为整数类型;如果我正确解释了标准,它也可以定义为nullptr_t。一个隐藏的(非规范性)脚注说 (void*)0 不能是一个有效的定义,这是有道理的,因为 C++ 没有 C 从 void* 到其他指针类型的隐式转换。

如果您有一个将其定义为((void*)0) 的C 编译器和一个将其定义为0L 的C++ 编译器,它们都是一致的。两种实现都满足用例(可以从NULL 分配和比较)。

C++ 引入nullptr 主要是为了提供适合重载的类型化值,因为将NULL 传递给函数可以选择整数版本被认为是令人惊讶的。

【讨论】:

  • 1.它驳斥了问题的前提——两个规范都说它是实现定义的,它必须是一个空指针常量。 2. C++ 标准准确地说是“The macro NULL is an implementation-defined null pointer constant. See also: ISO C 7.19”我不明白你怎么能比这更权威。
  • @einpoklum 0 和 0L 都是 C++ 中的空指针常量(无引号),因为标准是这样说的。
  • 在 C++ 中,(void*)0not 一个空指针常量 because
  • @einpoklum “空指针常量”是一个术语,它既是(a)常量,又是(b)在需要指针的上下文中表示空指针。 0Llong 类型的常量,绝对不是指针。这并不妨碍它成为一个空指针常量。如果你不喜欢这个词,你可以试着想出一个更好的词,也许足够多的人会喜欢它。如果您不喜欢 0L 能够代表空指针这一事实,那么运气不好,因为向后兼容。
  • @einpoklum:“说它是一个“空指针常量”是很糟糕的措辞”我不明白“糟糕的措辞”有什么关系.该标准对该术语及其使用方式有明确的定义。你可能不喜欢这个词,但它就是这样。
【解决方案4】:

我的假设一直是实现将NULL 保留为0 的决定是与C++98 向后兼容的问题。我已经看到很多代码在非指针上下文中使用NULL,特别是作为(可能是无意的,或者至少是不知情的)数组中空终止符的替代品。由于自 11 年以来的最佳实践是“根本不要使用 NULL”,将 NULL 重新定义为 nullptr 可能会破坏坏/旧代码,而对好/新代码的帮助却很少。

【讨论】:

  • 我首先问的是如何设置 NULL。一旦获得价值,他们自然不会改变它......
  • 呃...在 C++98 中,它还能是什么?他们没有远见制造nullptr,也无法使用(void*)0,所以他们真的只有一个选择。 (有一个可选的L 很好衡量。)
  • 1.早在 C++98 标准化之前就选择了一个值 2。您回答的问题与我提出的问题不同。
  • @einpoklum:“在 C++98 标准化之前就选择了一个值”一个不适用于 C++ 的值。我认为一个答案提到了这一点。
  • @einpoklum:在 C++98 标准化之前?您的意思是在早期的准标准 C++ 中,在禁止来自 void* 的隐式转换之后?一些早期的 C++ 实现是通过翻译成 C 来工作的,所以从 PoV 中选择一个不需要额外支持的定义当然是明智的,例如 0L这样他们就可以通过实现/设计语言的其余部分来获得一个。
【解决方案5】:

在 C 中,void* 可以隐式转换为任何 T*。因此,将NULL 设为void* 是完全合适的。

但这非常危险。所以 C++ 取消了这种转换,需要您手动进行大多数指针转换。但这会造成与 C 的源代码不兼容;以 C 想要的方式使用 NULL 的有效 C 程序将无法在 C++ 中编译。它还需要大量冗余:T *pt = (T*)(NULL);,这会令人恼火且毫无意义。

因此,C++ 将 NULL 宏重新定义为整数文字 0。在 C 中,the literal 0 is also implicitly convertible to any pointer type 并生成一个空指针值,这是 C++ 保留的行为。

当然,使用文字 0(或者更准确地说,一个值为 0 的整数常量表达式)作为空指针常量……不是最好的主意。特别是在允许重载的语言中。因此,C++11 将 NULL 完全放在一个专门表示“空指针常量”的关键字上,仅此而已。

【讨论】:

  • “文字 0 充当空指针常量”是 C++ 创新吗?像the C FAQ 这样的东西引用 K&R 说这也发生在 C 中。
  • @NathanPierson:可能是这样;我不太了解 C 的历史。
  • @NathanPierson:C 肯定有这个功能很长时间了(例如,它出现在我的 K&R 第二版 ANSI C 的纸质副本中,这是我拥有的最古老的 C 书籍),但很可能很早C 编译器需要将 NULL 定义为 ((void*)0)。除非 NULL 宏早于 C 中 void* 的引入,否则很可能就是这种情况; bell-labs.com/usr/dmr/www/chist.html 说 K&R 第一版没有提到 void 类型,该语言在 1982 年就有了。但仍然 NULL 可能早于此; stdio.h 是 非常 旧的,并且确实将其用于错误返回。
  • @PeterCordes 在“早期”K&R C 中,您不需要任何强制转换即可将整数转换为指针并返回(void 根本不是关键字),所以它会'投0毫无意义。像#define FOO 0170200 后跟struct foo { int bar; }; ...; FOO-&gt;bar = 17 这样的东西是完全有效的,并且在所有地方都可以使用。 FWIW,Unix 版本 7 的源代码有 #define NULL 0
  • @Peter,K&R 第一版没有“void”并将 NULL 定义为 0。(在示例代码中,第 97 页)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-02-04
  • 2010-09-30
  • 2014-05-10
  • 2011-08-22
  • 2011-12-07
  • 1970-01-01
相关资源
最近更新 更多