为什么gcc优化时默认使用size-aware delete操作符?
Why does gcc use the size-aware delete operator by default when optimizing?
如果我定义自己的 new
和 delete
运算符,如下所示:
#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 new
和 operator 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.
如果我定义自己的 new
和 delete
运算符,如下所示:
#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 new
和 operator 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.