【问题标题】:Can you please explain this C++ delete problem?你能解释一下这个 C++ 删除问题吗?
【发布时间】:2011-01-21 13:02:37
【问题描述】:

我有以下代码:

std::string F()
{
  WideString ws = GetMyWideString();

  std::string ret;
  StringUtils::ConvertWideStringToUTF8(ws, ret);
  return ret;
}

WideString 是第三方类,StringUtils 也是。它们对我来说是一个黑匣子。第二个参数通过引用传递。

当我单步调试调试器时,return ret 行会抛出一个令人讨厌的弹出窗口 (Visual C++),说堆可能已损坏。仔细检查返回的字符串副本是可以的,但删除ret 失败。 ret 在返回之前包含正确的值。

转换函数可能会导致这种情况吗?有什么想法可以解决吗?

更新:

  • 项目本身就是一个 dll
  • StringUtils 是一个库
  • 项目是针对多线程 CRT 编译的(不是调试,不是 dll)
  • 在 Visual Studio 之外运行时,程序似乎运行良好

【问题讨论】:

  • StringUtils是你自己编译的还是第三方库? std::string 的实现可能因编译器而异。
  • 那么要么它有一个错误,要么它的编译器设置与你的不同。 (我认为您确实使用与编译 lib 的编译器相同的编译器。)
  • @sbi:更有可能是 StringUtils 接口坏了。它不应该使用字符串或作为函数参数。
  • @sbi:约翰是绝对正确的,我会在对他的回答的评论中解释原因。
  • 这应该被标记为“onedefinitionrule”

标签: c++ corruption heap-memory one-definition-rule


【解决方案1】:

根据您显示的小代码,我想StringUtils::ConvertWideStringToUTF8()std::string& 作为第二个参数。鉴于此,我看不出您的代码如何导致堆损坏。

但是请注意,C++ 库的链接通常仅在所有代码都使用相同的编译器和相同的编译器设置编译时才有效。

【讨论】:

    【解决方案2】:
    1. 如果 StringUtils 是单独编译的(例如,使用不同的编译器版本),则对象布局可能会发生冲突。
    2. 如果 StringUtils 在 DLL 中,您必须确保它和主程序都被编译为使用 DLL 中的标准库。否则,每个模块(可执行文件和 DLL)都会有自己的堆。当 StringUtils 尝试使用从不同堆分配的字符串中的数据时,就会发生不好的事情。

    【讨论】:

    • 不仅必须在 DLL 中使用 std lib,而且必须使用该 DLL 的相同风格和版本。
    【解决方案3】:

    StringUtils 的设计者设计了一个非常糟糕的 API。 API 的公共接口中不应使用任何模板化的标准库类型。 std::string 被内联炸毁。因此,如果您使用的编译器和库与 StringUtils 的实现者使用的编译器和库不完全相同,则类型可能并且很可能会有所不同。从根本上说,StringUtils failed to separate the interface from the implementation 的实现者。

    问题的说明。假设您使用的是 MSVC 9.0 SP1,而我使用的是 MSVC 8.0。在我的编译器上,std::string 的实现可能如下所示:

    class string
    {
    // : :  stuff
    private:
      int someInt_;
      char* someBuf_;
    };
    

    ...但是在您的编译器上它可能看起来不同:

    class string
    {
    // : :  stuff
    private: 
    
      void* impl_;
    };
    

    如果我写一个库函数:

    void DoSomethingWithAString(std::string& str);
    

    ... 你调用它,你代码中的sizeof(string) 将不同于我代码中的sizeof(string)。类型不一样。

    您的问题实际上只有 2 个解决方案:

    1) [preferred] 获取 StringUtils 的实现者来修复他损坏的代码。

    2) 替换编译器使用的库以匹配 StringUtil 的实现者使用的库。假设他没有替换标准库的实现,您可能可以通过在与实现者使用的相同补丁级别使用相同的编译器来完成此操作。

    编辑:3) 第三种选择是停止使用 StringUtils。老实说,这可能是我会做的。

    【讨论】:

    • 根据您的推理,任何 C++ API 都是糟糕的 API。我不同意这种观点。
    • @sbi:那你不明白我的推理。 std::string 是内联的。实现因编译器而异,也因库而异。如果您使用 intel 编译器编写库,而我尝试使用 MSVC 使用它,std::string 对您和我来说看起来会有所不同。
    • 我并不是说所有 C++ 接口都很差。你是如何做出这种合乎逻辑的跳跃的?
    • @sbi:John 绝对正确,跨 DLL 边界使用 C++ 对象非常脆弱。仅使用纯虚拟接口或 C 兼容类型。当然,您可以提供与 C 兼容的内部 API,然后在头文件中提供包装器,然后 std::string 可以毫无问题地使用,因为它是在调用者的上下文中编译的。
    • @John:当使用不同的标准库实现/编译器/编译器版本/编译器设置编译时,您无法链接包含类的目标文件。无论对象文件是否包含内联代码都是如此。如果你说在 API 中使用 std::string 是愚蠢的,那么使用其他所有类也是愚蠢的,无论它是否包含内联代码。 (哪个班级没有?)
    【解决方案4】:

    您对StringUtilsWideString 的使用使您看起来像是在使用C++ Builder。您是否尝试混合使用 C++ Builder 模块和 Visual C++ 模块?如果是这样,那么您肯定会看到您所描述的问题。

    您不能将 Visual C++ std::string 传递给 C++ Builder 函数,因为 C++ Builder 代码将假定参数使用 C++ Builder 的 std::string 定义。这些类可能有不同的字段,它们共有的字段可能有不同的顺序。

    即使类具有相同的定义,模块仍将使用不同的内存管理器。被调用的函数将使用其内存管理器为新的字符串内容分配内存,调用者将使用自己的内存管理器稍后尝试释放字符串的内容。

    【讨论】:

    • 所以如果我提前预留容量应该没问题吧?
    • 我没有编译包含 StringUtils 的库。
    • 不太可能,库格尔。该函数可能不使用输入字符串中已分配的空间。此外,你忘记了我的第二段。无论如何,这个库是从哪里来的?
    • 它是 Adob​​e InDesign CS3 SDK 的一部分。
    猜你喜欢
    • 1970-01-01
    • 2015-11-27
    • 1970-01-01
    • 2016-12-30
    • 1970-01-01
    • 1970-01-01
    • 2022-11-24
    • 1970-01-01
    相关资源
    最近更新 更多