gpt4 book ai didi

c++ - 将空指针传递给新位置

转载 作者:IT老高 更新时间:2023-10-28 14:01:25 24 4
gpt4 key购买 nike

默认放置 new 运算符在 18.6 [support.dynamic] ¶1 中声明,具有不抛出异常规范:

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

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

-15-
[Note: unless an allocation function is declared with a non-throwing exception-specification (15.4), it indicates failure to allocate storage by throwing a std::bad_alloc exception (Clause 15, 18.6.2.1); it returns a non-null pointer otherwise. If the allocation function is declared with a non-throwing exception-specification, it returns null to indicate failure to allocate storage and a non-null pointer otherwise. —end note] If the allocation function returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.

在我看来(特别是对于放置 new,不是一般情况下)这种 null 检查是一个不幸的性能损失,尽管很小。

我一直在调试一些代码,其中在对性能非常敏感的代码路径中使用放置 new 以改进编译器的代码生成,并且在程序集中观察到 null 检查。通过提供特定于类的放置 new 重载,该重载使用抛出异常规范(即使它不可能抛出)声明,条件分支被删除,这也允许编译器生成更小的代码对于周围的内联函数。说放置new函数可以抛出,即使它不能抛出的结果是明显更好的代码。

所以我一直想知道放置 new 情况是否真的需要空检查。它可以返回 null 的唯一方法是如果你将它传递给 null。虽然写是可能的,而且显然是合法的:

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

我不明白为什么会有用,我建议程序员在使用放置 new 之前必须明确检查 null 会更好,例如

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

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

我想知道禁止将空指针传递给默认放置 new 函数是否合理,如果不是,是否有更好的方法来避免不必要的分支,而不是尝试告诉编译器该值不为空,例如

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();

注意这个问题被有意标记为微优化,我建议您为所有类型重载放置 new 以“提高”性能。这种影响在一个非常具体的性能关键案例中被注意到,并且基于分析和测量。

更新: DR 1748使用带有新位置的空指针使其行为未定义,因此不再需要编译器进行检查。

最佳答案

除了“有没有人需要放置 new 来正确处理空指针情况?”之外,我看不到很多问题。 (我没有),我认为这个案子很有趣,足以让人们对这个问题产生一些想法。

我认为标准被破坏或不完整 wrt 放置新功能和一般分配功能的要求。

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

If the allocation function is declared with a non-throwing exception-specification and returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.

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

[...] If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size. [...] The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type with a fundamental alignment requirement (3.11) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function).

以及§5.3.4,14:

[ Note: when the allocation function returns a value other than null, it must be a pointer to a block of storage in which space for the object has been reserved. The block of storage is assumed to be appropriately aligned and of the requested size. [...] -end note ]

显然,仅返回给定指针的放置 new 无法合理地检查可用存储大小和对齐方式。因此,

§18.6.1.3,1关于安置新说

[...] The provisions of (3.7.4) do not apply to these reserved placement forms of operator new and operator delete.

(我猜他们在那个地方没有提到§5.3.4,14。)

然而,这些段落间接说“如果你将垃圾指针传递给 palcement 函数,你会得到 UB,因为违反了 §5.3.4,14”。因此,由您来检查放置新位置的任何 poitner 的健全性。

本着这种精神,并通过重写 §5.3.4,13,标准可以从放置 new 中删除 noexcept,从而导致对间接结论的补充:“......如果你传递null,你也会得到UB”。另一方面,与空指针相比,指针未对齐或指向内存太少的可能性要小得多。

但是,这将消除检查 null 的需要,并且非常符合“不要为不需要的东西付费”的理念。分配函数本身不需要检查,因为 §18.6.1.3,1 明确说明了这一点。

为了总结,可以考虑添加第二个重载

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

遗憾的是,向委员会提出此建议不太可能导致更改,因为它会破坏现有代码,依赖于放置 new 可以使用空指针。

关于c++ - 将空指针传递给新位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17571103/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com