为什么gcc优化时默认使用size-aware delete操作符?

Why does gcc use the size-aware delete operator by default when optimizing?

如果我定义自己的 newdelete 运算符,如下所示:

#include <cstdio>
#include <cstdlib>
#include <new>

void* operator new (size_t count)
{
    printf("Calling custom new!\n");
    return malloc(count);
}

void operator delete(void *p) noexcept
{ printf("Called size unaware delete!\n");
  free(p);
}

int main()
{
    int *a = new int{1};
    delete a;
}

并使用 gcc 版本 12.1 进行编译,并指定 -O2 -Wall 选项,我收到 mismatched-new-delete 警告。查看编译后的输出,我发现编译器使用了大小感知删除运算符(签名 void operator delete(void *p, std::size_t sz);)而不是我定义的自定义删除运算符(有关详细信息,请参见 compiler explorer output)。

其他编译器(例如 clang)使用我定义的 delete 运算符,因此不会导致运算符不匹配警告。为什么 gcc 在优化时使用大小感知版本?

参考 post-C++20 草案 (n4861)。我还假设 C++14 或更高版本,它引入了 size-aware 释放函数。

对于您的特定示例,需要 delete 表达式来调用 size-aware operator delete,因为要销毁的类型是完整的。所以 GCC 的行为是正确的。 (参见 [expr.delete]/10.5

然而,选择 size-aware 不是问题,因为全局 operator delete(void*, size_t) 重载的默认行为如果不替换,就是调用相应的 operator delete(void*),所以最后还是会用到你自定义的实现。 (参见 [new.delete.single]/16

但是 [new.delete.single]/11 中有一个建议,即替换没有 size_t 参数的 operator delete 版本的程序也应该替换带有 size_t 参数的版本。注释阐明,尽管目前标准库提供的 size-aware 版本的默认行为无论如何都会调用自定义非 size-aware 实现,但在未来的标准修订中可能会发生变化。

此外,允许编译器在给定的示例中省略对 operator newoperator delete 的调用,以便以不同的方式提供存储或更可能只是省略整个主体main 没有其他可观察到的副作用。所以有可能根本没有调用 operator delete

因此,为了 future-proof 代码并避免 linter 警告,添加 size-aware 全局重载的替代品

void operator delete(void *p, std::size_t) noexcept
{
    ::operator delete(p);
}

另请注意,标准要求替换此重载的行为方式是始终可以通过调用 size-unaware 版本替换它而不影响内存分配。 (参见 [new.delete.single]/15


虽然从 C++14 开始就需要,但 Clang 似乎还没有默认启用 size-aware 释放函数。您需要添加 -fsized-deallocation 标志以启用它们。

一些关于默认启用它的补丁的讨论似乎正在进行 here


另请注意,您对 operator new 的实施已损坏。 operator new 的抛出版本不允许 return 空指针。所以你必须检查 malloc 的 return 值并抛出 std::bad_alloc 如果它是 null.