将空指针传递给新的位置
默认放置位置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_alloc
exception来分配存储失败(条款15,18.6.2.1); 否则返回一个非空指针。 如果使用非抛出exception规范声明分配函数,则返回空值以指示分配存储失败,否则返回非空指针。 – 注意 ]如果分配函数返回null,则不进行初始化,不应该调用解除分配函数,new-expression的值应该为空。
在我看来,(特别是安置new
,不是一般的)这个空检查是一个不幸的performance打击,尽pipe很小。
我一直在debugging一些代码,其中放置new
被用在一个性能敏感的代码path中,以改善编译器的代码生成,并且在程序集中检查了null。 通过提供一个特定于类的放置位置的new
重载,这个重载是通过抛出的exception规范来声明的(即使它不可能抛出),条件分支也被移除了,这也允许编译器为周围的内联函数生成更小的代码。 说安置new
function的结果可能会抛出,尽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
是有效的内存位置。)
我想知道是否合理禁止传递一个空指针默认的位置new
function,如果不是有没有更好的方法来避免不必要的分支,除了试图告诉编译器的值不为空例如
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 ++很乐意允许位移)?