【问题标题】:How would the standard allow indirection through a null pointer?标准如何允许通过空指针进行间接寻址?
【发布时间】:2011-10-27 16:05:36
【问题描述】:

编辑:这与C++ Standard Core Language Active Issues 上的活跃问题 232 相关

所以真正的问题是标准如何允许通过空指针进行间接寻址?

考虑以下代码:

struct a { int x; };
struct b { int y; };

struct c: a, b { };

b *f(c *pointer_to_c) { return pointer_to_c; }

f(...) 必须测试pointer_to_c 是否为NULL,如果是则返回NULL。 (NULL 指针始终是 NULL 指针,无论你如何转换它。)

现在,考虑以下几点:

b *f_ref(c &reference_to_c) { return &reference_to_c; }

第一个问题:f_ref 是否需要检查&reference_to_c 是否为NULL? 第二个问题:如果C++最终通过空指针定义了间接行为,这是否意味着f_ref必须检查NULL? (我想答案取决于标准允许的内容,即,如果它说“将 NULL 引用转换为基类未定义多重继承”,那么答案显然是否定的。)

对于最后一个难题,现在考虑一下:

const b *f_const_ref(const c &reference_to_c) { return &reference_to_c; }

f_const_ref 是否必须检查 &reference_to_cNULL?顺便说一句,在 VC++ 2010 中,所有三个函数都优化为 f,也就是说,所有三个函数都针对 NULL 测试输入。

因此,鉴于您不能在定义明确的程序中传递空引用,这是否意味着如果 C++ 标准最终允许通过空指针进行间接寻址,则它不会导致表达式或子表达式绑定到空指针并创建一个空引用?换句话说,如果标准允许通过空指针进行间接寻址,那么它们可以允许的各种方式是什么?

【问题讨论】:

  • 在 C++ 中不能有一个空引用,对吗?
  • 我以为你不能有一个空引用?为什么编译器需要检查指针是否为NULL 并返回NULL,如果它可以以任何一种方式返回值?我想我不明白很多东西。
  • 这并不是说取消引用空指针是可以的,它只是建议像&*p 这样的表达式应该是有效的,即使在*p 可能不是的某些情况下也是如此。不过仍然只是一个建议!

标签: c++ reference standards


【解决方案1】:

取消引用 NULL 指针是无效的(即未定义的行为)。

因此,永远不可能从 NULL 中获取引用(在有效代码中)。

因此,您获得的任何引用都不会为 NULL,因此您无需检查它。

第一个问题:f_ref 是否需要检查 &reference_to_c 是否为 NULL?

没有。

第二个问题:如果 C++ 最终通过空指针定义了间接行为,这是否意味着 f_ref 必须检查 NULL?

确实如此。其未定义的行为。因此,任何进一步的猜测都是毫无价值的。

对于最后一个难题,f_const_ref 是否必须检查 &reference_to_c 为 NULL?

没有。

【讨论】:

  • 关于语句f(...) 必须测试pointer_to_c 是否为NULL,如果是则返回NULL。 是这样吗?
  • 这句话毫无意义。如果 C 不是从 B 派生的,则为编译时错误。如果将 NULL 指针强制转换为基类,则结果为 NULL。
  • 那么完全错了吗?我是这么想的,那样做会很愚蠢。就像return ptr == NULL ? NULL : ptr;
  • @Martin,我的问题措辞不佳。我很好奇如果标准允许通过空指针进行间接访问,即 &(*(char *)0),该标准可以允许什么。
  • 好的。我知道您来自哪里,并且我同意对数组/容器末尾的引用应该是有效的(只要您不取消引用它)。但我不同意将 NULL 转换为引用的能力,这会打开一大堆使语言变得更复杂的蠕虫(因此我很高兴在当前的草案标准 (n3242) 中没有以这种方式解决它)。
【解决方案2】:

如果您在 f_reff_const_ref 中的引用为 NULL,则您深陷未定义行为领域。

这是未定义的,因为获得 NULL 引用的唯一方法是取消引用 NULL 指针。所以下面是未定义的,尽管在 g++ 4.5.2 上它会输出 0。

#include <iostream>
struct C {};
C* foo(C& r) {  return &r; }
int main() {
  C* c = NULL;

  std::cout << foo(*c) << std::endl;
  return 0;
}

【讨论】:

  • 为什么 g++ 让你在不崩溃的情况下取消引用空指针?
  • @Seth :因为编译时没有警告/错误是 UB 的无数表现之一,就像在运行时正确执行一样。
  • 主要是因为我既不读取也不写入地址零,所以它不会崩溃。
  • 在这个例子中没有空指针的解引用。标准中没有任何内容表明 foo(*c) 实际上会取消引用该指针。恰好相反;如何实现引用取决于实现。引用通常以指针的形式实现,因此foo(*c) 实际上只是在 g++(以及我所知道的所有其他实现)中按值传递。这个例子所做的是创建一个空引用,这是一种与取消引用空指针不同的未定义行为。
【解决方案3】:

C++ 确实 允许这样做。对 NULL 的引用是非法的,并且在标准方面将导致未定义的行为。

话虽如此,我从未见过编译器会做任何超出您期望的事情。

【讨论】:

  • 为什么对NULL的引用是非法的并且是UB? &amp;ref 不能为 NULL 和你所说的,是两个不同的东西。
  • @Nawaz:当然可以。 (不幸的是)创建空引用相当容易。
  • 事实上,我此时正在追查一个愚蠢的空引用错误。
  • 我遇到了这个错误。在一个关键点,我没有针对空指针的保护,我碰巧以一种生成空指针的方式调用我的代码。当我将*ptr 作为参数传递给以Foo &amp; ref 作为参数的函数时,C++ 很高兴地将该空指针转换为空引用。程序当然崩溃了,但是直到后来(很久以后)它试图用那个空引用做一些事情时才发生崩溃。
【解决方案4】:

&amp;reference_to_c 永远不能为 NULL。在函数参数中,必须使用某个有效对象来初始化引用。所以&amp;reference_to_c不能为NULL。

【讨论】:

  • 关于“必须使用一些有效对象初始化引用”:这仅在定义良好的程序中才是正确的。我不了解你,但在我的时间里,我写过很多定义不明确的程序。只有通过内部测试发现的所有错误和不满的客户的错误报告,程序才会变得相当明确。即使那样,也不能保证程序确实是定义良好的。如果代码不仅仅是一个玩具程序,那么几乎可以肯定在某个地方隐藏着一个潜在的错误。
  • 对假设的“空引用”进行测试仍然没有多大意义,因为您在创建 UB 之前很久就已经获得了它。在那之后,进一步的测试无论如何都不可靠,并且会被优化器删除(可以假设定义良好的代码)。
猜你喜欢
  • 1970-01-01
  • 2015-12-02
  • 2020-11-19
  • 1970-01-01
  • 1970-01-01
  • 2019-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多