将空指针传递给新的位置

默认放置位置new运算符在18.6 [support.dynamic]¶1中用非抛出exception规范声明:

 void* operator new (std::size_t size, void* ptr) noexcept; 

这个函数除了return ptr;外什么也不做return ptr; 所以它是合理的,但是根据5.3.4 [expr.new]¶15这意味着编译器必须检查它在调用对象的构造函数之前不会返回null:

-15-
[ 注意:除非使用非抛出exception规范(15.4)声明了分配函数,否则它表示抛出std::bad_allocexception来分配存储失败(条款15,18.6.2.1); 否则返回一个非空指针。 如果使用非抛出exception规范声明分配函数,则返回空值以指示分配存储失败,否则返回非空指针。 – 注意 ]如果分配函数返回null,则不进行初始化,不应该调用解除分配函数,new-expression的值应该为空。

在我看来,(特别是安置new ,不是一般的)这个空检查是一个不幸的performance打击,尽pipe很小。

我一直在debugging一些代码,其中放置new被用在一个性能敏感的代码path中,以改善编译器的代码生成,并且在程序集中检查了null。 通过提供一个特定于类的放置位置的new重载,这个重载是通过抛出的exception规范来声明的(即使它不可能抛出),条件分支也被移除了,这也允许编译器为周围的内联函数生成更小的代码。 说安置newfunction的结果可能会抛出,尽pipe它不能 ,是明显更好的代码。

所以我一直想知道是否真的需要空位检查new情况下安置。 它可以返回null的唯一方法是如果你通过它null。 虽然这是可能的,而且显然是合法的,写:

 void* ptr = nullptr; Obj* obj = new (ptr) Obj(); assert( obj == nullptr ); 

我不明白为什么这将是有用的,我build议,如果程序员必须在使用安置之前明确地检查空

 Obj* obj = ptr ? new (ptr) Obj() : nullptr; 

有没有人需要安置new的正确处理空指针的情况下? (即不添加明确的检查,即ptr是有效的内存位置。)

我想知道是否合理禁止传递一个空指针默认的位置newfunction,如果不是有没有更好的方法来避免不必要的分支,除了试图告诉编译器的值不为空例如

 void* ptr = getAddress(); (void) *(Obj*)ptr; // inform the optimiser that dereferencing pointer is valid Obj* obj = new (ptr) Obj(); 

要么:

 void* ptr = getAddress(); if (!ptr) __builtin_unreachable(); // same, but not portable Obj* obj = new (ptr) Obj(); 

注意这个问题是有意标记微优化,我不是build议你去重载所有你的types的new位置,以“提高”性能。 这种影响在一个非常具体的性能关键的情况下被发现,并基于分析和测量。

更新: DR 1748使其未定义的行为使用一个空指针与新的位置,所以编译器不再需要做检查。

虽然除了“有没有人需要放置新的来正确处理空指针的情况下,我看不到有太多的问题? (我没有),我觉得这个案子很有趣,足以让我对这个问题有一些想法。

我认为这个标准对于一般的分配职能的安排新function和要求来说是不完整的或不完整的。

如果仔细看看引用的第5.3.4.13节,这意味着每个分配函数都必须检查返回的空指针,即使它不是noexcept 。 所以应该改写成

如果分配函数是用非抛出exception规范声明的,并且返回null,则不应该进行初始化,不应该调用解除分配函数,并且newexpression式的值应该为空。

这不会损害分配函数抛出exception的有效性,因为它们必须遵守§3.7.4.1

[…]如果成功,它将返回一个存储块的开始地址,其字节长度至less与请求的大小一样大。 […]返回的指针应适当alignment,以便可将其转换为具有基本alignment要求(3.11)的任何完整对象types的指针,然后用于访问已分配存储中的对象或数组(直到存储通过调用相应的释放函数来显式释放)。

§5.3.4,14

[注意:当分配函数返回一个非空值时,它必须是一个指向一个存储块的指针,在这个存储块中已经保留了该对象的空间。 假定存储块被适当alignment并且被请求的大小。 […] – 结束]

显然,只是返回给定的指针的一个放置新的,不能合理地检查avilable存储大小和alignment。 因此,

§18.6.1.3.1关于安置新说

(3.7.4)的规定不适用于这些保留的运营商新的和运营商删除的布局forms。

(我想他们错过了在那个地方提到§5.3.4.14。)

然而,这些段落一起间接地说:“如果你传递一个垃圾指针到palcement函数,你会得到UB,因为§5.3.4,14被违反了”。 所以这是由你来检查任何poitner放置新的理智。

本着这种精神,通过重写§5.3.4,13,这个标准可以将noexcept从新的位置上noexcept ,导致除了这个间接的结论:“…如果你通过null,你也得到UB” 。 另一方面,与具有空指针的情况相比,不太可能存在错位的指针或指向内存过less的指针。

但是,这将消除检查null的需要,这将符合“不要付你不需要的东西”的理念。 分配函数本身不需要检查,因为§18.6.1.3,1明确地这样说。

为了整理,可以考虑增加第二个过载

  void* operator new(std::size_t size, void* ptr, const std::nothrow_t&) noexcept; 

不幸的是,向委员会提出这个build议不太可能会导致改变,因为它会破坏现有的依赖于空位的新代码。

一个非常有趣的问题(和随后的答案),感谢分享。

从我的angular度来看(不是基于任何特定的东西),我希望放置新的失败时返回一个空指针,而不是调用空对象的构造函数。 这样就和malloc一样。

你提出的build议是一个好主意 – 我用gcc 4.4.6testing了这一点,如果我用抛出的版本覆盖新的位置,我确实看到空指针检查消失。

一个问题 – 不§18.6.1.3,1说,新的放置不能移动(尽pipe海湾合作委员会和vc ++很乐意允许位移)?

Interesting Posts