为什么解除引用空指针是未定义的行为?

时间:2011-07-22 16:43:41

标签: c++ pointers undefined-behavior

根据ISO C ++,取消引用空指针是未定义的行为。我的好奇心是,为什么?为什么标准决定声明它未定义的行为?这个决定背后的理由是什么?编译器依赖?似乎没有,因为根据C99标准,据我所知,它是明确定义的。机器依赖?有什么想法吗?

13 个答案:

答案 0 :(得分:37)

为解除引用NULL指针定义一致的行为需要编译器在大多数CPU体系结构上的每次取消引用之前检查NULL指针。对于专为速度而设计的语言而言,这是一个不可接受的负担。

它还只修复了一个较大问题的一小部分 - 有很多方法可以使无效指针超出NULL指针。

答案 1 :(得分:23)

主要原因是,当他们编写原始C标准时,有许多实现允许它,但产生了相互矛盾的结果。

在PDP-11上,地址0始终包含值0,因此取消引用空指针也会给出值0.相当多的人使用这些机器感觉因为它们是原始机器C已经写在/用于编程,这应该被认为是所有机器上C的规范行为(即使它最初是偶然发生的)。

在其他一些机器上(想到Interdata,虽然我的记忆很容易出错)地址0正常使用,所以它可能包含其他值。还有一些硬件,其中地址0实际上是一些内存映射硬件,因此读/写它做了特殊的事情 - 完全不等同于读/写普通内存。

营地不会就应该发生什么达成一致,因此他们将其定义为未定义的行为。

编辑:我想我应该在编写C ++标准时添加它,它的未定义行为已经在C中很好地建立了,并且(显然)没有人认为有充分的理由在这一点上创建冲突所以他们保持不变。

答案 2 :(得分:11)

给定义行为的唯一方法是向每个指针取消引用和每个指针算术运算添加运行时检查。在某些情况下,这种开销是不可接受的,并且会使C ++不适合它经常用于的高性能应用程序。

C ++允许您创建自己的智能指针类型(或使用库提供的类型),其中包括在安全性比性能更重要的情况下进行检查。

根据C99标准的第6.5.3.2/4节,在C中取消引用空指针也是未定义的。

答案 3 :(得分:8)

来自@Johannes Schaub的回复

This 提出了一个有趣的理由,这似乎很有说服力。


仅仅取消引用空指针的形式问题是无法确定结果左值表达式的标识:取消引用指针所产生的每个这样的表达式必须在评估该表达式时明确地引用对象或函数。如果取消引用空指针,则没有此左值标识的对象或函数。这是标准用于禁止空引用的参数。

另一个引起混淆的问题是typeid运算符的语义很好地定义了这种痛苦的一部分。它表示,如果给出了一个左引用取消引用空值的左值,则结果会抛出bad_typeid异常。尽管如此,这是一个有限的区域,其中存在对上述寻找身份的问题的例外(没有双关语)。存在其他情况,其中对未定义的行为进行类似的异常(尽管不那么微妙并且在受影响的部分上有引用)。

委员会讨论了通过定义一种没有对象或函数标识的左值来全局解决这个问题:所谓的空左值。然而,这个概念仍有问题,他们决定 not to adopt it


注意:
将此标记为社区维基,因为答案&功劳应归于原始海报。我只是在这里粘贴原始答案的相关部分。

答案 4 :(得分:5)

真正的问题是,你会期待什么样的行为?

根据定义,空指针是表示缺少对象的单数值。取消引用指针的结果是获取对指向的对象的引用。

那么你如何从一个指向虚空的指针得到一个好的参考?

你没有。因此未定义的行为

答案 5 :(得分:1)

我怀疑是因为如果行为定义得很好,编译器必须在指针解除引用的任何地方插入代码。如果它的实现被定义,那么一种可能的行为仍然可能是一次严重的崩溃。如果没有指定,则某些系统的编译器会产生额外的不适当的负担,或者它们可能会生成导致硬崩溃的代码。

因此,为了避免任何可能的编译器负担,他们将行为定义为未定义。

答案 6 :(得分:1)

有时您需要无效指针(另请参阅Windows上的MmBadPointer),以表示“无”。

如果一切都有效,那就不可能了。因此,他们使NULL无效,并且禁止您取消引用它。

答案 7 :(得分:0)

根据原始C标准 NULL可以是任何值 - 不一定为零

语言定义指出,对于每个指针类型,都有一个特殊值 - “空指针” - 可以与所有其他指针值区分开,并且“保证与指向任何对象或函数的指针不相等”。 “也就是说,空指针明确指向无处;它不是任何对象或函数的地址

每个指针类型都有一个空指针,不同类型的空指针的内部值可能不同。

(来自http://c-faq.com/null/null1.html

答案 8 :(得分:0)

这是一个简单的测试&例如:

  1. 分配指针:

    int * pointer;

  2. ?指针创建时的值是什么? ?指针指向什么?
    ?当我在当前状态下取消引用这一点时会发生什么?

    1. 标记链表的结尾。 在链表中,节点指向另一个节点,但最后一个节点除外 最后一个节点中指针的值是什么? 当您取消最后一个节点的“下一个”字段时会发生什么?
    2. 需要是一个值,指示指针未指向任何内容或处于无效状态。这是NULL指针概念发挥作用的地方。链表可以使用NULL指针来指示列表的结尾。

答案 9 :(得分:0)

尽管在C / C ++中取消引用NULL指针确实从语言角度引出了未定义的行为,但是在编译器中为具有相应地址的内存的目标很好地定义了这种操作。在这种情况下,这种操作的结果只是简单地读取地址0处的存储器。

此外,只要不绑定引用的值,许多编译器就允许您取消引用NULL指针。这样做是为了提供与不符合要求但广泛使用的代码的兼容性,例如

#define offsetof(st, m) ((size_t)(&((st *)0)->m))

甚至有一个discussion使这种行为成为标准的一部分。

答案 10 :(得分:0)

在其他地方已经提出了这样的论点,即没有大量开销就不可能对空指针引用进行明确定义的行为,我认为这是事实。这是因为AFAIU在这里“定义明确”也意味着“便携式”。如果不对nullptr引用进行特殊处理,最终将生成指令,这些指令仅尝试读取地址0,但是在不同的处理器上会产生不同的行为,因此定义不明确。

所以,我想这就是为什么取消对nullptr(以及可能还有其他无效指针)的引用标记为未定义的原因。

我确实想知道为什么这是未定义的,而不是未指定的或实现定义的,这与未定义的行为不同,但是需要更高的一致性。

尤其是当程序触发未定义的行为时,编译器可以执行几乎所有操作(例如,丢弃整个程序吗?),并且仍然被认为是正确的,这有些问题。在实践中,您会期望编译器将对指向零地址的读取进行空指针引用的编译,但是随着现代优化器变得越来越好,而且对未定义的行为也更加敏感,我认为它们有时会做更多的事情彻底破坏程序。例如。考虑以下内容:

matthijs@grubby:~$ cat test.c
unsigned foo () {
        unsigned *foo = 0;
        return *foo;
}

matthijs@grubby:~$ arm-none-eabi-gcc  -c test.c -Os && objdump -d test.o 

test.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <foo>:
   0:   e3a03000        mov     r3, #0
   4:   e5933000        ldr     r3, [r3]
   8:   e7f000f0        udf     #0

该程序仅取消引用并访问空指针,这将导致生成“未定义指令”(在运行时暂停程序)。

当这是偶然的空指针取消引用时,这可能没问题,但是在这种情况下,我实际上正在编写一个需要以读取地址0(包含复位向量)的引导加载程序,因此这件事发生了。

因此,与其说是答案,不如说是这件事的一些额外视角。

答案 11 :(得分:-1)

因为您无法创建空引用。 C ++不允许它。因此,您无法取消引用空指针。

主要是未定义的,因为没有合理的方法来处理它。

答案 12 :(得分:-4)

您实际上可以取消引用空指针。有人在这里做了:http://www.codeproject.com/KB/system/soviet_kernel_hack.aspx