问题是在具有-02或以上优化的框架中重载运算符new和delete [关闭]

Posted

技术标签:

【中文标题】问题是在具有-02或以上优化的框架中重载运算符new和delete [关闭]【英文标题】:Issue is overloading operator new and delete in framework with -02 or above optimisation [closed] 【发布时间】:2018-04-09 17:23:29 【问题描述】:

我们正面临与 xcode 9.2 捆绑的 libc++ 的问题

场景

我们有一个重载运算符 new 和 delete 的框架。 这些运算符 new 和 delete 的定义隐藏在 dll 中,这里定义了苹果指南:https://developer.apple.com/library/content/technotes/tn2185/_index.html#//apple_ref/doc/uid/DTS10004200-CH1-SECTION13

我们还有一个应用程序链接到这个框架并重载了它自己的操作符 new 和 delete。

问题

现在,问题是如果在框架内创建一个字符串(std::string),它会调用应用程序端运算符 new 进行内存分配,但在销毁时,它正在调用 框架端运算符删除。由于应用程序端和框架端的堆实现不同,这可能会导致内存损坏,这绝对是一个问题。

此问题仅在使用 -o2 或以上优化级别时在框架的发布版本中观察到。如果我们将 -fno-inline 标志传递给编译器,则不会观察到此问题。 xcode 8.2 也没有观察到这个问题。

在进一步调查此问题后,我发现在当前版本的 libc++ 中内联了 basic_string 的析构函数,而与 xcode 8.2 捆绑的 libc++ 并非如此。 在clang论坛上有一些讨论:https://reviews.llvm.org/D24599

我的猜测是,由于在运行时链接期间内联,basic_string 的析构函数正在引用框架端运算符 delete 以释放分配的内存,但我需要确认我的理论。

如果是这种情况,那么我们是否应该使用 -fno-inline 标志构建我们的框架?如果我们使用此标志,是否会对性能造成重大影响,或者是否有其他我们应该考虑的方法?

重载运算符new和delete的定义:

void * operator new (size_t len) throw (std::bad_alloc)...

void * operator new( std::size_t len, const std::nothrow_t & _nothrow ) throw ()...

void * operator new[] (size_t len) throw (std::bad_alloc)...

void 运算符 delete ( void * ptr ) throw()...

void operator delete(void * ptr, const std::nothrow_t & _nothrow) throw ()...

void operator delete[] (void * ptr) throw()...

寻求帮助

如果需要,我会提供更多信息

【问题讨论】:

通常一些未定义的行为会被积极的优化暴露出来。在没有看到您的代码的情况下,我认为这是您将得到的最佳猜测。 请按照此处所述发布 MVCE:***.com/help/mcve 如果您谈论的是void* ::operator new(std::size_t)void ::operator delete(void*) 或类似的全局函数,那么拥有多个集合就违反了单一定义规则。由于这是未定义的行为,编译器和链接器系统可能会或可能不会容忍这种情况,并且可能会或可能不会产生有用的行为。我不认为准确追踪何时以及为什么会发生或不发生这种情况有多大价值。 了解as-if rule(也在wikipedia) 顺便说一句,xcode 是 IDE,而不是 compiler。它使用Clang 作为编译器 【参考方案1】:

来自技术说明:

您的替换将在应用程序范围内生效。甚至其他链接单元(共享库)中的代码也会调用您的自定义 new 和 delete。 在整个应用程序(所有链接单元)中,被替换的 new 和 delete 应该只有一个定义。 这将确保如果内存所有权跨共享库边界转移,它将被正确删除。

您的应用程序中有两个新/删除定义。这已经很糟糕了(tm)。

一般来说,共享库不应覆盖这些运算符,除非这是共享库的唯一工作。否则,应用程序可能会链接到多个覆盖的新/删除定义。在极少数情况下,共享库可能会发现这些运算符的私有定义很方便。这是通过链接 -unexported_symbols_list 文件名标志并将以下符号放在 unexport 文件中来完成的。

您能否再次检查库是否真的遵循了该规则 - 即没有导出符号?

在这样做时,作者必须确保内存所有权不会转移到此共享库中或从该共享库中转移出来。请注意,内存所有权转移可能以微妙的方式发生,例如传递引用计数对象(例如 std::string)、抛出包含堆分配消息的异常(例如 std::runtime_error)或内联资源分配构造函数, 对应的析构函数没有内联(反之亦然)。

我建议不要将 -fno-inline 标志用作“快速修复”,因为这可能只会隐藏当前明显的问题(多个新/删除定义),同时仍然允许进一步的内存损坏。如果您无法摆脱重复的定义(这将是 IMO 更好的选择),您只能尝试确保标头从不包含任何分配内存的内容,同时能够内联 - 但即使是我d 认为既困难又冒险。

【讨论】:

是的,我用 nm 工具验证过,框架没有导出任何版本的 operator new 和 delete。另外,正如我提到的,它在低于 9.2 的 xcode 中完美运行,所以可能是由于链接中提到的 basic_string 析构函数的内联而引入了这个问题。

以上是关于问题是在具有-02或以上优化的框架中重载运算符new和delete [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

C++ 大于等于运算符

了解下C# 运算符重载

重载运算符中的分段错误>>

运算符重载

php 重载等于运算符

重载 [] 和 = 运算符以在 C++ 中接受我的模板类的值